Handler所有问题灵魂拷问

  • Handler 机制中,存在哪些角色?各自承担了什么功能?

Handler:消息辅助类 & 对外的接口 & 向 MQ 投递消息 & 消息的目标处理者;
Message:消息的载体 & 被 Handler 投递 & 自带 Handler 处理 & 自带消息池;
Looper:循环器 & 持有 MQ & 循环从 MQ 中获取消息 & TLS 线程唯一;
MessageQueue:基于时间的优先级队列 & 链表结构 & Java 与 C++ 层的纽带;

  • Looper死循环为什么不会导致应用卡死

答: 因为当Looper处理完所有消息的时候会进入阻塞状态,当有新的Message进来的时候会打破阻塞继续执行。

这其实没理解好ANR这个概念。当我发送一个绘制UI 的消息到主线程Handler之后,经过一定的时间没有被执行,则抛出ANR异常。Looper的死循环,是循环执行各种事务,包括UI绘制事务。Looper死循环说明线程没有死亡,如果Looper停止循环,线程则结束退出了。Looper的死循环本身就是保证UI绘制任务可以被执行的原因之一。同时UI绘制任务有同步屏障,可以更加快速地保证绘制更快执行。

理解原因:
1.界面的绘制本身就是这个循环内的一个事件
2.界面的绘制是通过了同步屏障保护下发送的异步消息,会被主线程优先处理,因此使得界面绘制拥有了最高的优先级,不会因为 Handler 中事件太多而造成卡顿。

  • 可以在子线程直接new一个Handler吗

可以在子线程直接new一个Handler,不过需要在一个线程里需要先调用Looper.prepare()和Looper.loop()方法。

Thread {
            Looper.prepare()
            object : Handler() {
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)

                }
            }
            Looper.loop()
        }.start()

在主线程中为什么没看到Looper.prepare()?因为系统已经在应用启动的main方法里面调用Looper.prepareMainLooper()。Looper启动了handler的机制才能够正常运行,启动之前有需要prepare去创建Looper。如果不去调用loop()方法开启循环读取消息,你就算用handler传递了消息,没有去取消息的呀。

    public static void main(String[] args) {
      Looper.prepareMainLooper();
      Looper.loop();
    }
  • MessageQueue内部是什么样的数据结构?

MessageQueue内部存有Message对象,Message内存存有一个next的Message对象,就形成了message->next->message->next ...。
那么就形成了一个单链表。

public final class MessageQueue {
    Message mMessages;

    Message next() {
              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());
                }
    }
}

public final class Message implements Parcelable {
        @UnsupportedAppUsage
    /*package*/ Message next;
}

再看将消息入列的enqueueMessage方法:

    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) {
                // New head, wake up the event queue if blocked.
                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;
            }
        }
    }

enqueueMessage()方法里面,通过延迟时间when,去遍历消息队列的延迟时间,依次作比较,如果值小于某个消息的值,就会将该消息插入到这个消息前一个位置,实现一个节点的插入。为什么这里是一个队列呢?因为取消息的时候永远是从一个方向去取,永远是从头部开始取,那这不就是一个队列吗?

又因为发送消息最终是sendMessageAtTime(),可以控制传送的时间先后,将时间短的插入到先发送但是延迟时间长的节点前面去,所以MessageQueue是一个单链表的优先级队列。所以在loop方法中queue.next()能依次取出下一个消息,就是因为链表的结构。

  • Handler为什么会造成内存泄露?

handler创建的时候,是生成了一个内部类,内部类能直接使用外部类的属性和方法,内部类是默认持有外部类的引用,即Activity。而在handler调用enqueueMessage方法时,将自己赋予了msg的target属性,所以msg是持有handler的引用的。如果某个消息因为延迟执行或者没有处理完成,消息会一直挂载。msg持有handler,handler持有activity导致activity无法回收,造成了内存泄露。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
             long uptimeMillis) {
        msg.target = this;
        return queue.enqueueMessage(msg, uptimeMillis);
    }

解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在
Acitivity 的 onDestroy()中调用 handler.removeCallbacksAndMessages(null)及时
移除所有消息。

  • Hander机制采用的是什么设计模式?

采用的是生产者/消费者的设计模式。子线程生产消息,主线程消费消息,MessageQueue为生产仓库。

  • Handler是怎么实现切换线程的?

    var handler = object: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("minfo", "从子线程收到消息 ${msg.what}")
        }
    }

        object : Thread() {
            override fun run() {
                handler.sendEmptyMessage(200)
            }
        }

在一个线程中创建handler,然后在另一个创建的线程中调用该handler的发送消息,该handler中能接收到消息,即实现了线程间通信,那么handler是如何实现线程切换的呢?

当在A线程中创建handler的时候,同时创建了Looper与MessageQueue,Looper在A线程中调用loop进入一个无限的for循环从MessageQueue中取消息。当B线程调用handler发送一个message的时候,会通过msg.target.dispatchMessage(msg);将message插入到handler对应的MessageQueue中,Looper发现有message插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程的目的。

  • handler.sendMessage()与handler.post()的区别?

1、如果msg.callback不为空,也就是通过post方法发送消息的时候,会把消息交给这个msg.callback进行处理,然后就没有后续了。
2、如果msg.callback为空,也就是通过sendMessage发送消息的时候,会判断Handler当前的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage处理。

所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给msg.callback还是 Handler.Callback或者Handler.handleMessage。

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }
  • MessageQueue是怎么增删消息的?

                msg.next = p; // invariant: p == prev.next
                prev.next = msg;

添加消息:在enqueMessage方法中,将该条msg按照时间大小,遍历链表到正确的位置,将该msg插入到该节点中。

                        msg.next = null;
                        msg.markInUse();
                        return msg;

void markInUse() {
        flags |= FLAG_IN_USE;
    }

删除消息:在next方法中,将该msg对象的next指向null,并将flags标识为FLAG_IN_USE。

  • 一个线程可以有几个Handler?几个Looper?几个MessageQueue?

一个线程可以有几个Handler?

可以创建无数个Handler,但是他们使用的消息队列都是同一个,也就是同一个Looper。Handler在哪个线程创建的,就跟哪个线程的Looper关联,也可以在Handler的构造方法中传入指定的Looper,Looper.loop()循环读取消息。

那同一个Looper是怎么区分不同的Handler的?因为在msg入队列时,会将msg.target设置一个handler,处理消息的时候,也会调用msg对象的target去处理消息。

一个线程有几个looper?如何保证的?

Looper类中部分代码:

   final MessageQueue mQueue;
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

从Looper的源码中可以看到,在一个线程中初始化Looper,是用ThreadLocal存储Looper对象。同时保证一个线程只拥有一个Looper,存入looper之前,从sThreadLocal.get()中取看是否存过looper, sThreadLocal中如果存在则会抛出异常,保证一个线程中的looper实例唯一,且一旦存入,不可修改不可新增。

几个MessageQueue

一个线程的looper只会创建一次,只有一个looper对象,一个looper下只有一个MessageQueue属性,所以一个线程只有一个MessageQueue。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?

一个线程中包含多个Handler对象时,在消息的添加分发时,通过Message的target(Handler)标注,处理消息会去除对应的target去调用handleMessage,所以不会错乱。

  • ThreadLoacal的原理?

这里见我的另一篇ThreadLoacal的原理

  • 在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?

在消息处理结束后,其回调至Runnable 的run方法中,需要注意的是这里的仍然是在UI线程中,因为我们创建的Handler是在UI线程中,且Handler将Runnable内部封住成Message的形式,所以最终调用的是Runable中的run()方法,因此还是回到UI线程中,可以更新UI界面。

  • 主线程为什么不用初始化Looper?

Android程序的入口在ActivityThread的main方法中,在main方法中已经调用了prepareMainLooper()去创建looper对象了,并且调用了 Looper.loop()。如果是自己创建的子线程中,需要自己初始化looper并调用loop方法。ActivityThread 不继承自 Thread,它只是一个运行在主线程上的对象。

public static void main(String[] args) {
    ...
 // 初始化主线程Looper
    Looper.prepareMainLooper();
    ...
    // 新建一个ActivityThread对象
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
 
    // 获取ActivityThread的Handler,也是他的内部类H
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
 
    ...
    Looper.loop();
}
  • Handler如何保证MessageQueue并发访问安全?

各个线程都往一个messageQueue中存取msg,在存取数据的时候,是通过synchronized来保证了线程的安全性,使用messageQueue作为对象锁。各个子线程和主线程都是往用一个messageQueue存取消息,对调用同一个MessageQueue对象的线程来说,它们都是互斥的,所以保证了并发访问安全。

boolean enqueueMessage(Message msg, long when) {
     synchronized (this) {
          ......
 }
}

Message next() {
     synchronized (this) {
          ......
 }
}
  • 能不能让一个Message加急被处理?

可以 / 一种使得异步消息可以被更快处理的机制。
而看了前面 MessageQueue::next 的代码我们知道,当 MessageQueue 中遇到了一个同步屏障,则它会不断地忽略后面的同步消息直到遇到一个异步的消息,这样设计的目的其实是为了使得当队列中遇到同步屏障时,则会使得异步的消息优先执行,这样就可以使得一些消息优先执行。

    Message next() {
        for (;;) {
            if (msg != null && msg.target == null) {
                    // 同步屏障,找到下一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
        }
    }
  • Message 的同步屏障有什么用?有什么意义?如何发送一个同步屏障?

在 Handler 中还存在了一种特殊的消息,它的 target 为 null,并不会被消费,仅仅是作为一个标识处于 MessageQueue 中。它就是 SyncBarrier (同步屏障)这种特殊的消息。

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

移除同步屏障

    public void removeSyncBarrier(int token) {
      ...
    }

比如 View 的绘制过程中的 TraversalRunnable 消息就是异步消息,在放入队列之前先放入了一个消息屏障,从而使得界面绘制的消息会比其他消息优先执行,避免了因为 MessageQueue 中消息太多导致绘制消息被阻塞导致画面卡顿,当绘制完成后,就会将消息屏障移除。

  • 同步屏障的使用场景

貌似在开发的过程中,我们很少用到同步屏障,那么源码中在哪里用到了?

Android中的UI消息就是异步消息,需要优先处理 比如View更新,调用onDraw、requestLayout、invalidate等view最终都会调用到ViewRootImpl类中的scheduleTraversals方法。

void scheduleTraversals(){
   if(!mTraversalScheduled){

   mTraversalScheduled=true;
    //开启同步屏障
   mTraversalBarrier=mHandler.getLooper().getQueue().postSyncBarrier();
   //发送异步消息
   mChoreographer.postCallback();
   ...
   }
}

这里开启了同步屏障,并发送了异步消息,由于UI更显相关的消息是优先级最高的,这样系统就会优先处理这些异步消息了。

当然处理完消息后要移除同步屏障,这个时候就调用到了ViewRootImpl#unscheduleTraversals()。

  • 什么是异步消息?如何发送

意义:需配合同步屏障使用,否者与同步消息无区别;
异步消息:setAsynchronous(true) → 向 flags 添加 FLAG_ASYNCHRONOUS 标记
发送方式 通过异步 Handler 发送 → 构造 Handler 时,async 传递 true 发送消息前,主动调用 setAsynchronous(true)
安全起见,Android 9.0 普通开发者无法使用异步消息,所有发送方式被标记为 @hide

将handler标识标位异步handler,该handler就发送异步消息。

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
  • Handler的阻塞唤醒机制是怎么回事?

当消息不可用或者没有消息的时候就会阻塞在next方法,会进入nativePollOnce()方法,而阻塞的办法是通过pipe/epoll机制。

在enqueueMessage方法中,如果有新的消息进入,会根据needWeak字段,调用nativeWake()方法进行唤醒。

  • Handler 里藏着的 Callback 能干什么?

Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截
(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;
如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时
被 Callback 以及 Handler 处理。

  • 什么是IdleHandler?

当MessageQueue没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue还会做一件事,就是检查是否存在IdleHandler,如果有,就会去执行它的queueIdle方法。

当没有消息处理的时候,就会去处理这个mIdleHandlers集合里面的每个IdleHandler对象,并调用其queueIdle方法。 最后根据queueIdle返回值判断是否用完删除当前的IdleHandler。

    private IdleHandler[] mPendingIdleHandlers;

Message next() {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                //当消息执行完毕,就设置pendingIdleHandlerCount
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                //初始化mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //mIdleHandlers转为数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍历数组,处理每个IdleHandler
            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);
                }

                //如果queueIdle方法返回false,则处理完就删除这个IdleHandler
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
        }
    }
  • IntentService是啥?有什么使用场景?

public abstract class IntentService extends Service {

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

这就是一个可以在子线程进行耗时任务,并且在任务执行后调用stopSelf()自动停止的Service。

  • HandlerThread是啥?有什么使用场景?

public class HandlerThread extends Thread {
      @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

HandlerThread就是一个封装了Looper的Thread类。就是为了让我们在子线程里面更方便的使用Handler。

  • Handler 分发事件优先级,是否可拦截?拦截的优先级如何?

可以统一拦截消息,但无法拦截通过Runnable通过getPostMessage(Runnable r)生成的Message。
因为它msg.callback不为空会优先处理msg.callback,不会经过统一的Hanlder的mCallback。

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // mCallback 处理完如果返回 false,还是会继续往下走,再交给 Handler.handleMessage 处理的
            // 所以这边可以通过反射去 hook 一个 Handler ,可以监听 Handler 处理的每个消息,也可以改 msg 里面的值
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
  • 主线程 Looper 何时运行?

App 启动时,会调用到 ActivityThread 中,Looper 就在其 main() 方法中被启动;main() 中会主动调用 Looper.prepareMainLooper() 和 Looper.loop()

  • Handler 的 Message 可以分为那 3 类?分别有什么标识?

1.发送普通消息
2.发送异步消息
给msg对象加入设置异步的标识。

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

3.同步屏障
使用postSyncBarrier方法发送同步屏障,使msg.target == null。

private int postSyncBarrier(long when) {
      ...
}
  • 点击页面上的按钮后更新TextView的内容,谈谈你的理解?

点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障)

  • 同一个 Message 对象能否重复 send?

关键在于如何定义同一个 Message。

角度一:Java 对象层面,可被复用;
原因:Message 由消息池维护,即同一个对象被回收后会被再次复用;| new Message & Message.obtain()
角度二:业务层面,不能复用;
原因:Message 通过 enqueueMessage() 入队时,会通过 markInUse() 标记,再次入队无法通过 isInUse() 检查,则抛出异常;
  • Looper.loop 中,如果没有待处理的消息,为什么不会阻塞 UI?

主线程在 MessageQueue 没有消息时,会阻塞在 loop 的 queue.next() 方法中的 nativePollOnce()方法里。

此时主线程会释放 CPU 资源进入休眠状态,直到下一个消息到达或者有事务发生,通过往 pipe 管道写端写入数据的方式,来唤醒主线程。这里采用的是 epoll 机制。

epoll 机制是一种 IO 多路复用机制,可以同时监控多个描述符,在有事件发生的时候,立即通知相应程序进行读或写操作,类似一种 callback 的回调机制。主线程在大多数时候是处于休眠状态,并不会消耗大量的 CPU 资源。当有新的消息或事务到达时,会立即唤醒主线程进行处理,所以对用户来说是无感知的。

  • Looper 的 Printer 输出的日志,有什么其他用途?依靠的原理是什么?有什么缺点?

用途:性能监控;
原理:通过筛选日志内存,区分 Message 的开始执行和结束执行的时间点,即可判断处理 Message 的耗时,即主线程卡顿耗时;
缺点:Printer 存在大量字符串拼接,在消息量大时,会导致性能受损;| 实测数据:存在 Printer 时,在列表快速滑动时,平均帧率降低 5 帧;

  • Handler 可以 IPC 通信吗?

不能;Handler是一种共享内存的通信方式,Handler 只能用于共享内存地址的 2 个线程通信,即同进程的 2 个线程通信;

  • 为什么系统不建议在子线程访问UI?(为什么不能在子线程更新UI?)

如果采用多线程访问UI会出现线程安全,那为什么不加锁呢?

加锁会降低UI访问的效率。本身UI控件就是离用户比较近的一个组件,加锁之后自然会发生阻塞,那么UI访问的效率会降低,最终反应到用户端就是这个手机有点卡。
所以,Android设计出了单线程模型来处理UI操作,再搭配上Handler,是一个比较合适的解决方案。

  • 子线程访问UI的 崩溃原因 和 解决办法?

1.在ViewRootImpl创建之前进行子线程的UI更新,比如onCreate方法中进行子线程更新UI。
2.子线程切换到主线程进行UI更新,比如Handler、view.post方法。
3.给操作的view设置大小为matchparent或者

  • Message可以如何创建?哪种效果更好,为什么?

Message.obtain来创建Message,这样会复用之前的Message的内存,不会频繁的创建对象,导致内存抖动。

  • Message消息被分发之后会怎么处理?消息怎么复用的?

在一个message对象调用了dispatchMessage之后,会进行回收操作。
在recycleUnchecked方法中,释放了所有资源,然后将当前的空消息插入到sPool表头。
这里的sPool就是一个消息对象池,它也是一个链表结构的消息,最大长度为50。
使用obtain来获取复用消息,直接复用消息池sPool中的第一条消息,然后sPool指向下一个节点,消息池数量减一。

    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
  • 主线程中能否调用quit()方法?

是不能的。它会抛出一个异常,让程序挂掉。

  • 修改了手机系统时间,handler的延时消息收到也会发生改变吗

发送延时消息,会调用sendMessageDelayed,然后调用sendMessageAtTime,将一个时间点传入,该时间为SystemClock.uptimeMillis()

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    /**
     * Returns milliseconds since boot, not counting time spent in deep sleep.
     *
     * @return milliseconds of non-sleep uptime since boot.
     */
    @CriticalNative
    native public static long uptimeMillis();

SystemClock.uptimeMillis() 是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调用该方法时相差的毫秒数

System.currentTimeMillis() 代表的是从 1970-01-01 00:00:00 到当前时间的毫秒数,我们可以通过修改系统时间达到修改该值的目的,所以该值是不可靠的值

  • handler怎么移除一个消息

通过调用:

Handler().removeMessages(what: Int)

调用了handler中:

    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }

进入removeMessages方法:

    void removeMessages(Handler h, Runnable r, Object object) {
        if (h == null || r == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.callback == r
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.callback == r
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

removeMessages会将handler对应message queue里的消息清空,如果带了int参数则是对应的消息清空。1、这个方法使用的前提是之前调用过sendEmptyMessageDelayed(0, time),意思是延迟time执行handler中msg.what=0的方法;
2、在延迟时间未到的前提下,执行removeMessages(0),则上面的handler中msg.what=0的方法取消执行;
3、在延迟时间已到,handler中msg.what=0的方法已执行,再执行removeMessages(0),不起作用。

  • handler.removeCallbacksAndMessages(null)是什么意思

如果需要删除handler所有的消息和回调函数,那就需要使用handler.removeCallbacksAndMessages(null)。
这样做的好处是在Acticity退出的时候,可以避免内存泄露,因为有延迟消息,msg持有handler,handler持有activity,activity退出需要移除所有消息。
会while循环遍历msg消息链表,调用msg的recycleUnchecked()进行回收,方法会将msg所有属性置空和置0。

所以,防止handler内存泄露,一种解决方案是static+弱引用,另一种方案就是在activity的onDestroy中调用handler.removeCallbacksAndMessages方法。

参考:

https://blog.csdn.net/javine/article/details/45953575
https://blog.csdn.net/bzlj2912009596/article/details/79736912
//www.greatytc.com/p/7f1c46fb55c8
https://blog.csdn.net/qq_39477770/article/details/109331658?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
https://blog.csdn.net/u012165769/article/details/114681388?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
https://blog.csdn.net/u012165769/article/details/113531570
https://blog.csdn.net/weixin_39952074/article/details/111249137
说一下Handler的同步屏障机制?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348