Android 11 源码分析 :致敬Handler(2)面试点

846 阅读4分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

四个组成类各自的意义

  • Message :作为消息的数据结构。(内部的几个关键字段上一篇文章已经讲过)
  • MessageQueue :作为存储消息的数据结构,是一个以时间为顺序的有序列表。
  • Handler: 负责消息的发送和接收
  • Lopper: 开启无限循环从MessageQueue里取消息,如果取到了则执行消息内部的target.dspatchMessage(msg)

为什么我们在主线程创建Handeler没有写Looper.prepare()和Looper.loop()两个方法

为什么Android能一直运行

android.app.ActivityThread
   .......
   final H mH = new H();
   .......
    public static void main(String[] args) {
        .......
        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

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

ActivityThread 的 main 方法是一个新的 App 进程的入口,在创建ActivityThread实例之前,就执行了Looper.prepareMainLooper();

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

prepareMainLooper内部还是执行了prepare方法,同时将主线程的sMainLooper赋值。 随后执行了Looper.loop();开启循环

主线程在app启动的main方法里执行了Looper.prepare()和Looper.loop() App能一直运行的原因是因为main方法内有一个死循环。

queue.next()什么时候为null

我们知道Looper.loop()开启了一个死循环,循环退出的唯一条件是queue.next()=null queue指的是MessageQueue,所以看看他的next啥时候返回null

# android.os.MessageQueue

 Message next() {
          for (;;) {
           ......
          // Process the quit message now that all pending messages have been handled.
           if (mQuitting) {
               dispose();
               return null;
           }           
           ......
          }

next()方法很长,但是我们只需要找return null的代码快即可。mQuitting看命令的意思是正常退出的时候。也就是说队列正在退出的时候,返回null。就意味着循环结束。再看看在什么地方将mQuitting设为true的。

忽然想想。。loop方法的结束意味着main方法的结束。app的关闭,那我们是不是想办法把主线程的消息队列的mQuitting置为false就会有意想不到的效果发生?

通过AS代码查找,可以发起全局只有一个地方执行了mQuitting = true 操作。代码如下

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

注意第一行,如果mQuitAllowed为false的时候会抛异常,大意是主线程不允许退出。

在执行MessageQueue的quit方法时,mQuitting为true。而这个方法是在Looper中触发

# android.os.Looper

    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }

在Looper执行quit或者quitSafely后,queue.next()返回null

Looper的构造方法什么时候穿true或false,对应的MessageQueue有什么区别

为什么main方法不会阻塞主线程

可以留意一下prepare方法这里传递的是false,我们调用Looper.prepare()的时候默认传递是true,我们还知道这个值是给MessageQueue的quit方法里,有一行 用的所以去看看有什么区别

# android.os.MessageQueue

     private final boolean mQuitAllowed;
     MessageQueue(boolean quitAllowed) {
        赋值给了mQuitAllowed 
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

在上文MessageQueue的quit方法里,执行quit的时候如果mQuitAllowed为false的时候会抛异常,大意是主线程不允许退出。

Looper构成方法传的值影响其内部的MessageQueue是否是可退出队列,只有主线程会false。所以我们主线程内也是不可以执行Looper.quit的。

为什么Loop.loop死循环不会阻塞主线程。这是因为在next方法内有一会native代码。内部做了处理。代码如下

# android.os.MessageQueue

 Message next() {
   ......
   nativePollOnce(ptr, nextPollTimeoutMillis);
   ......
}

一个线程能拥有几个Handler,Looper

通过上一篇文章,我们知道Looper.prepare方法只能调用一次,多次调用会报错,因为prepare方法其实是往线程本地变量存一个Looper。我们在分析Activity的时候,知道主线程有一个叫prepare的Handler。

所以一个线程可以拥有多个Handler,但是只会有一个Looper

不执行parper或多次执行parper会怎样。

不执行Looper.loop会怎样

与上面问题类似,不写Looper.prepare在创建Handlder时会抛异常,多次调用。会在调用的时候就抛异常 不执行Looper.loop则发的消息没人去取。所以不会执行任何消息

runuithread方法为什么可以执行在主线程

runuithread这个api想必大家用的很香,能在子线程中快速的切换到主线程执行代码。其实内部还是用的Handler机制

# android.app.Activity

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

代码中的mHandler是Activity中的Handler实例。运行在主线程中

runuithread方法其实就是在主线程执行了Handler.post(Runnable)而已

Handler 中使用了什么设计模式

 android.os.Message
 
 private static Message sPool;
 Message next;

享元模式 主要体现在Message内部。因为sPool和next变量可以看出来是享元模式。 这里不细说,知道即可,感兴趣的可以去看看享元模式

Looper的quit和quitSafely区别

上午代码贴出来过,其实内部都是调用MessageQueue的quit方法只是参数不同。区别是quit是直接退出,quitSafely会等列表内的消息都被消费完再退出。

创建Handler的时候使用参数为Callbacl的形式

这种方式不会派生子类。(具体使用场景我也不知道。)

关于Handler还有啥问题。。暂时想不到了。欢迎补充