Handler那些事儿(1)

1、一个线程有几个Handler?

你在MainActivity new一个Handler,在LoginActivity也可以new 一个Handler,甚至在Fragment也可以new一个Handler,而这些线程均是主线程,所以一个线程可以有多个Handler;

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

一个线程只有一个Looper。那如何保证一个线程只有一个Looper呢?首先通过下面的Looper源码,我们看到Looper是通过调用静态方法prepare()进行初始化的,而prepare()又调用了prepare(true)方法。

looper2.png

其中prepare(true)的true表示创建的子线程的Looper是一定能被停止的。

looper3.png

在该方法里面,首先调用了通过sThreadLocal调用了get()。sThreadLocal是ThreadLocal的实例,内部维护了一个和HashMap类似的key-value键值对,只是略微有所不同,其内部是通过一个静态类Values的table数组来保存键值对的,如table[0]存储的是ThreadLocal实例本身,而table[1]存储的是Looper实例。所以sThreadLocal.get()的目的是判断当前线程是否已经创建了Looper了。如果没有创建,则通过Looper构造方法创建了一个新的实例,里面初始化了MessageQueue(消息队列),然后使用sThreadLocal.set(…)进行关联。

那Looper是如何和线程Thread关联上的呢?查看下方ThreadLocal的部分源码,上方sThreadLocal.set(…)方法内部通过Thread.currentThread()获取到当前的线程currentThread,也就是给当前线程加入了指向Looper的变量,然后通过values(currentThread)获取到Thread的变量localValues,其类型为ThreadLocal.Values,然后再赋值给values,如果为null,则进行初始化,最后将ThreadLocal和Looper put进values里。这样当前线程就和Looper关联到了一起。

那如何保证唯一性呢? 我们再翻看源码,在Looper源码里,只有一个地方调用了sThreadLocal.set(…),这也保证了Looper被初始化的唯一性。

looper4.png

3.Handler内存泄露原因?为什么其他的内部类没有说有过这个问题?

首先是因为内部类持有外部类的引用,导致即使Activity等执行了onDestory()也不会释放,因为jvm识别到该activity被引用了,所以不会对其进行回收,导致内存泄露。那为什么其他的内部类没有说有过这个问题呢?主要是Handler可以发送延时类消息,消息不到时间不会被销毁。同时MessageQueue持有Message,Message持有了Handler,而Handler持有了Activity,只有消息被销毁了,才会销毁Handler,Handler才会销毁Activity。所以Handler内部类持有外部类的引用才会发生内存泄露。那如何解决Handler的内存泄露问题呢?可以把 Handler 对 Activity 弱引用,这样 GC 就能把 Activity 及时的回收,从而杜绝了内存泄露的问题。

4.为什么主线程可以new Handler()呢?如果想要在子线程中new Handler()需要做些什么准备?

在ActivityThread(主线程)里有一个main()函数,它是每一个Android应用最早执行的函数。如下源码:


public static void main(String[] args) {

  .....

  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  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);
  Looper.loop();

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

可以看到,ActivityThread(主线程)的main函数已经执行了Looper.prepareMainLooper()和Looper.loop()。我们再来看看Looper.prepareMainLooper()的源码:

public static void prepareMainLooper() {
     prepare(false);
     synchronized (Looper.class) {
          if (sMainLooper != null) {
              throw new IllegalStateException("The main Looper has already been prepared.");
          }
          sMainLooper = myLooper();
     }
}

我们看到,其内部使用prepare(false)初始化了一个Looper。所以说主线程一启动,在main()函数中,由系统已经帮我们完成执行prepare() 和 loop()了,所以可以直接new Handler(),不需要再进行Looper初始化了。并且主线程中的所有代码,全都运行在这两个函数(prepareMainLooper()/prepare() 和 loop())之间。所有的线程都必须要prepare()和loop(),如果子线程中想要进行Handler操作,就必须在子线程中执行prepare() 和 loop()。

5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

当MessageQueue没有Message时,loop()会阻塞状态,会一直卡在Message msg = queue.next()里。那此时我们需要调用Looper的quitSafely()或quit()函数,其内部均会调用MessageQueue的quit()把消息队列中的全部消息给移除掉,这样就释放了内存。而在quit()函数的最后一行,会执行一个nativeWake()函数,唤醒queue.next()里的nativePollOnce(ptr, nextPollTimeoutMillis),唤醒后,才能往下执行,发现 Message msg = mMessages是空的,就会执行dispose()和return null,此时Looper就跳出了死循环,释放线程。综上所述,子线程中维护的Looper,消息队列无消息的时,可以调用Looper的quitSafely()或quit()函数,使得Looper结束死循环,并且起到释放内存和线程的作用。

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

在往MessageQueue添加数据时,使用syncchronized线程锁,在往MessageQueue取数据时,也使用syncchronized线程锁,保证消息不会混乱。

7.我们使用Message时应该如何创建它?

由于Message创建非常频繁,如果不断以new的方式去创建它,会造成内存抖动。所以Message使用了享元设计模式,在销毁时并没有真正的销毁,只是将消息的内容置空了。在创建的时候,使用Message.obtain()进行创建,其内部会从队列头部取出Message对象复用。

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

使用Handler的postDelayed(@NonNull Runnable r, long delayMillis)发送延时消息后,MessageQueue里的消息会进行重新排序。该postDelayed内部会调用sendMessageDelayed(@NonNull Message msg, long delayMillis)方法,sendMessageDelayed又调用enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis)方法,最后该enqueueMessage方法会调用MessageQueue的enqueueMessage方法。MessageQueue的enqueueMessage方法会根据消息的时间(当前时间+延迟时间)与消息队列里面的消息的时间进行对比查找,找到需要插入的位置然后插入。在MessageQueue中,所有的Message是以链表的形式组织在一起。MessageQueue有着队列的性质,只能从队首删除,队尾添加(MessageQueue不只是可以从队尾添加)。在使用时都是通过Looper.loop()从消息队列中取出消息,而且loop方法就是从MessageQueue的队首开始取出消息。如果MessageQueue为空,此时调用enqueueMessage会通过内部的nativeWake方法唤醒消息队列,消息队列被唤醒后,next方法会被执行,从而触发计算该Message执行需要等待的时间,计算完之后重新等待,直到时间到了才会触发相应操作。

总之,使用Handler的postDealy后消息队列可能会进行重新排序。消息队列里消息按执行先后时间进行排序,先执行的在前,后执行的在后。postDealy发送的消息会根据延迟时间与消息队列里存在的消息的执行时间进行比较,然后寻找插入位置插入消息。

9. Looper死循环为什么不会导致应用卡死?

我们先看主线程ActivityThread的main方法的源码:

public static void main(String[] args) {
    Looper.prepareMainLooper();//创建Looper和MessageQueue对象,用于处理主线程的消息

    ActivityThread thread = new ActivityThread();
    thread.attach(false);//建立Binder通道 (创建新线程)

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

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    //如果能执行下面方法,说明应用崩溃或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

我们在Launch桌面点击app的图标时,会先向AMS请求启动根Activity,AMS又会向zygote请求启动应用程序进程socket,zygote会创建并启动应用程序进程,应用程序进程准备就绪后返回给AMS,AMS再启动根Activity。最终进入到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法。下面是loop方法循环:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    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();
    for (;;) {
        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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);
 
        msg.recycleUnchecked();
    }
}

通过创建一个for死循环来处理消息 msg.target.dispatchMessage(msg);

真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。主线程的死循环一直运行不会特别消耗CPU资源在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以说主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

发表回复

后才能评论