Handler相关

什么是Handler机制

在Android开发的过程中,会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),因为UI的更新要通过Main thread来进行(其实特殊子线程也可以更新UI)。那么这里就涉及到了如何将子线程的数据传递给主线程的问题。
所以Android系统提供了一个消息传递的机制——Handler,可用于将子线程的数据传递给主线程,其实,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。

发送消息

   new Thread(new Runnable() {
        @Override
        public void run() {
            Message msg = Message.obtain();
            msg.arg1 = 1;
            msg.arg2 = 2;
            Bundle bundle = new Bundle();
            bundle.putChar("key", 'v');
            bundle.putString("key", "value");
            msg.setData(bundle);
            mHandler.sendMessage(msg);  
        }
    }).start();

Handler机制运行流程

在子线程执行完耗时操作,当 Handler 发送消息时,将会 调用MessageQueue.enqueueMessage ,向消息队列中添加消息。当通过 Looper.loop
开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next ,然后
调用目标 Handler (即发送该消息的 Handler )的 dispatchMessage 方法传递消息,
然后返回到 Handler 所在线程,目标 Handler 收到消息,调用 handleMessage
法,接收消息,处理消息。

Handler、MessageQueue、Looper三者之间的关系:

一个线程中只能存在一个 Looper,Looper 是保存在 ThreadLocal 中的。主线程(UI 线程)已经创建了一 个 Looper,所以在主线程中不需要再创建 Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个 Handler,即一个 Looper 可以处理来自多个 Handler 的消息。 Looper 中维护一个 MessageQueue,来维护消息队列,消息队列中的 Message 可以来自不同的 Handler。

一、Handler机制四大核心类

  • Handler:消息辅助类,负责发送并处理消息,(主要功能向消息池发送各种消息事件
    (Handler.sendMessage) 和处理相应消息事件 (Handler.handleMessage))
  • Message:需要传递的消息,可以传递数据;
  • MessageQueue:消息队列,负责消息的存储和管理,但是它的内部实现并不是用的队列,实际上是通过
    一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优
    势。主要功能向消息池投递消息 (MessageQueue.enqueueMessage) 和取走消息池
    的消息 (MessageQueue.next)
  • Looper:负责关联线程以及消息的分发,不断循环执行(Looper.loop),从 MessageQueue 中读取消息,按分发机制将消息分发给目标处理者

在这里插入图片描述

消息机制的原理,以及从源码角度来解析消息机制的运行过程。可以简
单地用下图来理解。

二、Handler源码分析

通过阅读Handler类源码可以看到如图以下发送消息的方法最终都是调用了MessageQueue的enqueueMessage方法。

在这里插入图片描述

三、MessageQueue源码分析

1、MessageQueue队列
1)MessageQueue是一个优先级队列,优先级是以要延迟执行的时间为判断依据,时间越短,执行优先级越长,如果延迟执行时间都为0,则插入到此节点的前面。
2)如果执行时间不为0,传过来的时间是按入参+系统时间为值,系统时间是越来越大,所以不可能出现时间相等的情况(时间为0除外)。
这里可以查看Handler中的sendMessageDelayed源码,如下所示:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        // 延迟时间是调用了系统时间+延迟时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


3)MessageQueue是在Looper类的 构造方法中 创建的,所以 一个Looper 对应一个 MessageQueue;

2、enqueueMessage方法
enqueueMessage方法主要作用是往消息队列中存消息,即入队方法。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // 注释1:当不存在消息或者当前要执行的时间为0,也就是立即执行或者当前延迟执行时间小于队列中的消息延迟时间的时候
        // 将当前Message插入到队列最前面的位置(延迟执行时间为0的都插入到队列的最前面),如果线程阻塞则唤醒
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 根据时间的顺序调用 msg.next,给每一个消息指定它的下一个消息是什么
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            // 注释2:如果不满足上述条件,则需要遍历整个消息队列,依次判断每个Message要延迟执行的时间,对比之后插入到其中,如果比某个几点要延迟执行的时间小,则插入到这个节点的前面进行执行
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                // 如果判断时间比某个节点小则跳出for循环,之后执行插入到此节点的前面的逻辑
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            // 执行插入到比此时间短的节点的前面的逻辑
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}


这段代码有几个点要分析一下:

  • 注释1:当不存在消息或者当前要执行的时间为0,也就是立即执行或者当前延迟执行时间小于队列中的消息延迟时间的时候,将当前Message插入到队列最前面的位置(延迟执行时间为0的都插入到队列的最前面),如果线程阻塞则唤醒。
  • 注释2:如果不满足上述条件,则需要遍历整个消息队列,依次判断每个Message要延迟执行的时间,对比之后插入到其中,如果比某个几点要延迟执行的时间小,则插入到这个节点的前面进行执行。

3、next方法
next方法负责从消息队列中取出Message,方法返回Message 对象。

  • 注释1:执行循环,不断从队列中获取Message,因为可能有Message插入到队头部,所以不断循环获取;
  • 注释2:获取当前系统时间;
  • 注释3:获取队列头消息;
  • 注释4:队头消息时间大于当前时间,即队头消息还没到执行时间;
  • 注释5: 当开始执行时,就从队列中取出Message进行返回。
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;
        // 注释1:执行循环,不断从队列中获取Message,因为可能有Message插入到队头部,所以不断循环获取
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 调用native层的nativePollOnce方法,
            nativePollOnce(ptr, nextPollTimeoutMillis);
            
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                // 注释2:获取当前系统时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // 注释3:获取队列头消息
                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) {
                    // 注释4:队头消息时间大于当前时间,即队头消息还没到执行时间
                    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 {
                        // 注释5: 当开始执行时,就从队列中取出Message进行返回
                        // 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;
                }
                ......
        }
    }


四、Looper源码分析

1、loop方法

  • 注释1:如果当前线程没有创建looper则抛出异常,所以要在子线程先执行Looper.prepare()方法;
  • 注释2:执行for循环,开始获取消息;
  • 注释3:不断执行queue.next()方法,从MessageQueue获取消息,如果消息还没到执行时间,则处于阻塞等待状态;
  • 注释4到注释5: Message类的target属性就是handler,这里调用handler的dispatchMessage方法,dispatchMessage又会调用handleMessage方法,这里就回到了创建handler的回调方法中,就可以在主线程中更新UI。
public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            // 注释1:如果当前线程没有创建looper则抛出异常,所以要在子线程先执行Looper.prepare()方法
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 获取MessageQueue 实例
        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.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;
        // 注释2:执行for循环,开始获取消息
        for (;;) {
            // 注释3:不断执行queue.next()方法,从MessageQueue获取消息,如果消息还没到执行时间,则处于阻塞等待状态
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            // 注释4: 打印日志,开始分发Message
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                   // 注释5: Message类的target属性就是handler,这里调用handler的dispatchMessage方法,dispatchMessage又会调用handleMessage方法,这里就回到了创建handler的回调方法中,就可以在主线程中更新UI
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }
             // 打印日志,结束分发Message
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            // 回收消息,释放资源
            msg.recycleUnchecked();
        }
    }


问题

一、为什么谷歌要提出:“UI更新一定要在UI线程里实现”这一规则呢?

目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:

Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。

二、 为什么用异步消息处理方式可以更新UI?

因为 handler 依赖于创建 handler 对象所在的线程,比如在主线程中创建handler对象,在子线程中不能直接更新UI,需要通过一系列的 发送消息、入队、出队等操作,最后调用到 handler.handleMessage(msg) 方法,此时 handleMessage()方法已经在主线程中,所以就可以在 handleMessage() 方法中更新UI。

在这里插入图片描述

三、一个线程可以创建几个Handler?

一个线程可以创建多个handler,比如每个Activity都可以创建Handler,说明UI线程可以创建多个handler。

四、一个线程有几个Looper,如何保证?

一个线程只创建一个Looper。在使用Handler机制之前,需要先调用prepare方法,源码如下,这段代码做了两件事:
第一件事:从sThreadLocal中获取Looper,如果能获取到,则抛出异常,从而保证一个线程只有一个Looper;
第二件事:在prepare方法内new一个Looper,然后放到ThreadLocal中。

private static void prepare(boolean quitAllowed) {
    // 从`sThreadLocal`中获取Looper,如果能获取到,则抛出异常,从而保证一个线程只有一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // new一个Looper,然后放到ThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}


查看ThreadLocal类的set方法,可以发现,ThreadLocal会把当前ThreadLocal的实例作为key,传入的Looper作为value,存入到数组中(ThreadLocal是以一个数组来维护<key, value>,方便每个线程处理自己的状态而引入的一个机制。)

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}


五、Handler内存泄露原因?

Handler会造成内存泄露是因为内部类持有了外部类的引用造成的,至于是如何持有外部类的,如下所示: 

@SuppressLint("HandlerLeak")
 private Handler handler = new Handler() {
     @Override
     public void handleMessage(@NonNull Message msg) {
         super.handleMessage(msg);
         // jvm的可达性
         MainActivity.this.click();
     }
 };
 
 private void click() {
     
 }


在handleMessage方法中,调用了外部类MainActivity的click方法,这就是内部类持有外部类的引用。当MainActivity销毁的时候,handler还没有执行完延迟消息,就一直持有外部类的引用。
那接下来分析是如何造成内存泄露的,查看Handler的enqueueMessage方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 把当前的handler对象赋值给Message
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}


会发现msg.target = this;会把当前的handler对象赋值给Message,而Message在未处理完之前会一直存在内存当中,所有Message会一直持有Handler的引用,Handler又会持有外部类的引用,所以如果长时间未回收Message,就可能造成内存泄露。

为何其他内部类没有造成内存泄露,比如ViewHolder?
因为MessageQueue中的Message的处理,是有延迟时间的,如果延迟时间太长,就会一直持有外部类引用,从而造成内存泄露。

解决该问题的最有效的方法是: Handler 定义成静态的内部类,在内部持有
Activity 的弱引用,并及时移除所有消息
private static class SafeHandler extends Handler {
        private WeakReference<HandlerActivity> ref;

        public SafeHandler(HandlerActivity activity) {
            this.ref = new WeakReference(activity);
        }

        @Override
        public void handleMessage(final Message msg) {
            HandlerActivity activity = ref.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

并且再在 Activity.onDestroy() 前移除消息,加一层保障:

   @Override protected
    void onDestroy() {
        safeHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
这样双重保障,就能完全避免内存泄露了。
注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。

六、为何主线程可以new Handler?如果想在子线程中new Handler需要做什么准备?

之所以可以在主线程中创建Handler是因为在Framwork层的ActivityThread类中的main方法中已经创建了主线程的Looper和执行loop循环;
主线程中的所有代码逻辑都是执行在主线程的Looper中。可以查看ActivityThread类中的H类,H类继承于Handler类,处理所有的消息逻辑。  

public static void main(String[] args) {
        // 省略代码。。。。。。
        // 注释1:创建主线程的Looper
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 注释2:执行loop循环
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }


那么如果想在子线程中new Handler需要做什么准备?
就如同主线程一样,需要先调用prepare()方法来创建一个Looper,然后再执行loop()方法开启Looper的循环。

 new Thread(new Runnable() {
        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
            Looper.prepare();
            threadHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    // 会收到消息
                    Log.e( "run: ", "接收到消息");
                }
            };
            // 会打印
            Log.e( "run: ", "loop"+Thread.currentThread().getId());
            Looper.loop();
            // 不会打印,因为loop一直处于阻塞状态,所以不会向下执行,整个子线程都卡到这里
            Log.e( "run: ", "循环外Log不打印");
        }
    }).start();


七、子线程的Looper,当消息队列中没有消息的时候的处理方案是什么?有什么用?

需要调用Looper的quitSafely()函数,quitSafely()函数会调用MessageQueue的quit方法,源码如下: 

void quit(boolean safe) {
      // 注释1:如果是主线程,不允许退出
     if (!mQuitAllowed) {
         throw new IllegalStateException("Main thread not allowed to quit.");
     }

     synchronized (this) {
         if (mQuitting) {
             return;
         }
         mQuitting = true;
        // 注释2:移除队列中所有的Message
         if (safe) {
             removeAllFutureMessagesLocked();
         } else {
             removeAllMessagesLocked();
         }

         // We can assume mPtr != 0 because mQuitting was previously false.
         // 注释3:当移除掉所有的Message之后,唤醒Looper循环
         nativeWake(mPtr);
     }
 }
  • 注释1:如果是主线程,不允许退出;
  • 注释2:移除队列中所有的Message,释放内存;
  • 注释3:当移除掉所有的Message之后,会唤醒Looper循环,当Looper循环被唤醒后,会触发next()方法的nativePollOnce(ptr, nextPollTimeoutMillis);方法,将阻塞的loop循环唤醒,而取出的Message就为null,next方法就会返回null,代码如下:
if (mQuitting) {
    dispose();
    return null;
}


next方法返回null,在loop方法中就会直接return;

Message msg = queue.next(); // might block
if (msg == null) {
   // No message indicates that the message queue is quitting.
   return;
}


这时loop中的无限for循环就会停止,代码就会往下执行下去。整个子线程的代码才能执行完成,从而释放线程。

new Thread(new Runnable() {
          @SuppressLint("HandlerLeak")
          @Override
          public void run() {
              Looper.prepare();
              threadHandler = new Handler(){
                  @Override
                  public void handleMessage(Message msg) {
                      super.handleMessage(msg);
                      // 会收到消息
                      Log.e( "run: ", "接收到消息");
                  }
              };
              // 会打印
              Log.e( "run: ", "loop"+Thread.currentThread().getId());
              Looper.loop();

              // 调用threadHandler.getLooper().quitSafely();方法后,message为null则会打印下面日志,执行完子线程代码
              Log.e( "run: ", "循环外Log不打印,需要释放Message,调用threadHandler.getLooper().quitSafely();");
          }
      }).start();


八、既然可以存在多个Handler往MessageQueue中添加消息(发消息时各个Handler可能处于不同的线程),那它内部是如何保证线程安全的呢?

MessageQueue的enqueueMessage方法和next方法都是通过synchronized来保证线程安全的。

1.消息入库enqueueMessage()方法中的 这个锁,说明的是对所有调用同一个MessageQueue 对象的线程来说,他们都是互斥的,然而,在我们的 Handler 里面,一个线程是对应着一个唯一的Looper 对象,而 Looper 中又只有一个唯一的 MessageQueue 。所以,我们主线程就只有一个 MessageQueue 对象,也就是说,所有的子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱。
2.消息出库next()方法中 由于 synchronized this )作用范围是所有 this 正在访问的代 码块都会有保护作用,也就是它可以保证 next 函数和 enqueueMessage 函数能够实现互斥。这样才能真正的保证多 线程访问的时候 messagequeue 的有序进行。
 

九、Message该如何创建?

Message可以通过new Message()进行创建,但这种创建Message会造成内存抖动,所以正确的创建Message应该调用Hand而的
obtainMessage方法,handler的obtainMessage方法会调用Message的obtain方法,源码如下:

 public static Message obtain(Handler h, int what) {
     Message m = obtain();
     m.target = h;
     m.what = what;

     return m;
 }


然后再找到obtain()方法,源码如下:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            // 链表结构的Message池子
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}


这段代码表示如果Message池中有创建的Message就可以复用,不用频繁的new Message(),如果没有,再new一个Message。
这样就可以避免频繁创建Message从而造成内存抖动。
当移除Message的时候,也不是真正的删除掉一个Message,而是重置这个Message的状态参数,当size大于50个的时候才销毁。源码如下:

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        // 回收
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}


找到Message的recycleUnchecked方法,重置Message状态,这里用的是享元设计模式(如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象。目的是提高系统性能)。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}


十、使用Handler的postDelay后消息队列会有什么变化?

使用Handler的postDelay后,enqueueMessage方法中会执行nativeWake(mPtr);唤醒方法,队列被唤醒,loop继续执行next方法,在next方法中会对比执行时间和当前时间,计算出一个消息等待时间,

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);
}


然后程序下一次for循环,执行nativePollOnce(ptr, nextPollTimeoutMillis);方法,让消息队列进入等待状态,等时间到后再唤醒。

十一、Looper的死循环为什么不会导致应用卡死?

在点击Launch桌面的图标第一次启动Activity时,会最终走到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法。

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程肯定不能运行一段时间后就自动结束了,那么如何保证一直存活呢?
简单的做法就是可执行代码能一直执行下去,死循环便能保证不会被退出,所以主线程是一直运行在Looper的loop死循环中,所以Looper只有一直循环下去,主线程才能一直执行下去。

除了主线程还有binder线程也是采用死循环方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单的死循环,无消息时会休眠,但是死循环又如何处理其他事物呢??通过创建新的线程。真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。

主线程的死循环一直运行会不会特别消耗CPU资源呢?
其实不然这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。另外主线程的唤醒可以通过输入事件或者发送Message来触发。


十二消息机制之同步屏障
 

参考:https://juejin.im/post/6844903910113705998

线程的消息都是放到同一个 MessageQueue 里面,取消息的时候是互斥取消息,而且只能从头部取消息 而添加消息是按照消息的执行的先后顺序进行的排序 ,那么问题来了,同一个时间范围内的消息,如果它是需要立刻执行的,那我们怎么办,按照常规的办法,我们需要等到队列轮询到我自己的时候才能执行哦,那岂不是黄花菜都凉了。所以,我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。
同步屏障是什么?
屏障的意思即为阻碍,顾名思义, 同步屏障就是阻碍同步消息,只让异步消息通过
Android Message 类通过 target 和 isAsynchronous() 组合出三种不同类型的 Message。

图片

三者的区别

本来只有普通消息,但是因为事情有轻重缓急,所以诞生了同步屏障消息和异步消息。它们两是配套使用的。当消息队列中同时存在这三种消息时,如果碰到了同步屏障消息,那么会优先执行异步消息。

图片

  1. 绿色表示普通消息,很守规矩,按照入队顺序依次出队;

  2. 红色表示异步消息,意味着它比较着急,有优先执行的权利;

  3. 黄色表示同步屏障消息,它的作用就是警示,后续只会让异步消息出队,如果没有异步消息,则会一直等待;

图片

上图,消息队列中全是普通消息。那么它们会按照顺序,从队首依次出队列。msg1->msg2->msg3。

图片

上图,三种类型消息全部存在,msg1 是同步屏障消息。同步屏障消息并不会真正执行,它也不会主动出队列,需要调用 MessageQueue 的 removeSyncBarrier() 方法。它的作用就是 "警示",后续优先让红色的消息出队列。

  1. msg3 出队列。

图片

  1. msg5 出队列。

图片

  1. 此刻 msg2 并不会出队列,队列中已经没有了红色消息,但是存在黄色消息,所以会一直等红色消息,绿色消息得不到执行机会。

图片

  1. 调用 removeSyncBarrier() 方法,将 msg1 出队列。

图片

  1. 绿色消息按顺序出队。

postSyncBarrier() 和 removeSyncBarrier() 必须成对出现,否则会导致消息队列出现假死情况。

总结:

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Vue中的handler通常指的是事件处理函数,也可以称为事件处理器。在Vue中,我们可以通过v-on指令来绑定事件处理函数,例如: ``` <template> <button v-on:click="handleClick">点击我</button> </template> <script> export default { methods: { handleClick() { console.log('按钮被点击了') } } } </script> ``` 在上面的例子中,我们为按钮绑定了一个click事件的处理函数handleClick。当按钮被点击时,控制台会输出"按钮被点击了"。这就是一个简单的事件处理函数。 ### 回答2: Vue的handler(处理器)是Vue框架中的一个概念。在Vue中,handler是一种用于处理事件或响应数据的函数或方法。 在Vue中,常见的使用handler的场景包括: 1. 事件处理:Vue中的事件处理使用handler来处理触发的事件。通过在Vue组件中定义事件处理函数,并将其作为handler绑定到对应的DOM元素上,当触发事件时,handler将会被自动调用。 2. 数据响应:在Vue中,可以使用handler来处理数据的响应。通过在Vue实例中定义响应式的数据,并使用handler来监听这些数据的变化,当数据发生改变时,handler会被自动触发,从而更新相关的视图或执行其他操作。 3. 计算属性:在Vue中,计算属性也可以使用handler来实现相应的逻辑。通过定义一个计算属性,并将其handler设置为响应式数据的监听器,当相关的响应式数据发生变化时,handler会被自动调用,返回计算属性的值。 总结来说,Vue中的handler是用于处理事件或响应数据的函数或方法。它可以用于处理事件的触发、数据的响应以及计算属性的实现等场景。通过使用handler,我们可以方便地处理Vue中的各种操作,并实现页面的交互和数据的更新。 ### 回答3: Vue的handler是Vue框架中的一个重要概念,用于处理事件的函数。 在Vue中,我们可以使用v-on指令来监听DOM事件,并将事件的处理程序(handler)定义为Vue实例中的一个方法。这个方法将会在相应的事件被触发时被调用。 handler在Vue中扮演了事件处理的角色,它可以接收事件对象作为参数,用于获取事件的相关信息。在handler中,我们可以编写逻辑代码来响应事件,并对应用状态进行相应的修改。 handler除了可用于监听DOM事件外,还可以用于监听自定义事件或是Vue实例自身的生命周期钩子函数。它可以是一个直接定义在Vue实例中的方法,也可以是一个引用了其他方法的函数。 使用handler的好处在于,它可以将事件处理逻辑与模板代码进行分离,提高代码的可维护性和可读性。同时,handler可以方便地管理和处理多个不同的事件,并且可以实现事件传参和事件修饰符等高级特性。 总之,Vue的handler是用于处理事件的函数,通过定义handler,我们可以监听和响应各种事件,并实现相应的业务逻辑。它是Vue框架中非常重要的一部分,能够帮助我们构建交互丰富的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值