handler源码解析

1. Handler 概念

handler是android中一种异步回调机制,android的消息机制主要就是指handler的运行机制、可以完成任意两个线程之间的通信。handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程去执行。很多人认为Handler的作用是更新UI,这的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景。

2. Handler运行机制的主要组成部分

Android中的异步消息处理主要由4个部分组成:Handler、Message、MessageQueue和Looper。下面对这四个部分简单介绍一下:

  1. Handler
    Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的 handleMessage()方法中。
  2. Message
    是在线程之间传递的消息对象,它可以在内部携带一些参数,可以在不通线程之间通信。
  3. MessageQueue
    MessageQueue是消息队列的意思,它是一个单项链表,用来存在handler发送的消息对象。
  4. Looper
    Looper是MessageQueue的管家,Looper初始化的时候会自动创建一个与之对应的MessageQueue对象。调用自己的Loop()方法之后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage方法中。

了解了Message、 Handler、MessageQueue以及Looper的基本概念后,我们通过更新UI的方式
再来把异步消息处理的整个流程梳理一遍。首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler的sendMessage()将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行U1操作了。整个异步消息处理机制的流程示意图如下图所示。
在这里插入图片描述
一条Message经过这样一个流程的辗转调用后,也就从子线程进入到了主线程,从不能更新UI变成了可以更新UI,整个异步消息处理的核心思想也就是如此。

3. handle简单使用

使用很简单,此处仅仅是做个演示。

/**
* 子线程与主线程进行通信,传递数据,在主线程中更新TextView
*/
public class HandlerSimpleUseActivity extends AppCompatActivity {
    private TextView handlerTv;
    private Button handlerBtn;
   

    private Handler handler1 = new Handler(){
        // 处理消息
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    handlerTv.setText(msg.obj.toString());
                    break;
                default:
                    break;
            }
        }
    };

    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handler_simple_use_actity);

        handlerBtn = findViewById(R.id.handler_btn);
        handlerTv = findViewById(R.id.handler_tv);
        handlerBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (v.getId()){
                    case R.id.handler_btn:
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                Message message = new Message();
                                message.obj = "普通handler构造使用";
                                message.what = 1;
                                // sendMessage方法发送消息
                                handler1.sendMessage(message);
                            }
                        }).start();
                        break;
                    default:
                        break;
                }
            }
        });
    }
}

4. Handler源码分析

4.1 Handler构造函数

public class Handler{

    // 已过时
    @Deprecated 
    public Handler() {
        this(null, false);
    }

    // 已过时 
    @Deprecated
    public Handler(@Nullable Callback callback) {
        this(callback, false);
    }

    // 推荐使用
    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }

    // 推荐使用
    public Handler(@NonNull Looper looper, @Nullable Callback callback) {
        this(looper, callback, false);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}

其中

// 不推荐的构造方法
public Handler();
public Handler(Handler.Callback callback);
// 推荐构造方法
public Handler(Looper looper);
public Handler(Looper looper, Handler.Callback callback);

不推荐的构造函数官方注释意思是说隐式的选择Looper可能会存在一些没有预期的错误。所以不推荐,推荐使用限制指定的Looper的构造方法。

说一下Handler构造函数中的参数

  • callback
public class Handler {

    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     * callback是回调接口,可以在实例化Handler时使用,以避免必须实现Handler的自己的子类。
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }
}

在handler的运行机制中。handler发送一个消息,添加到MessageQueue中。Looper不断循环从MessageQueue中取出消息,然后把这个消息分发到handler中,handler的handleMessage处理消息。而分发的处理过程就是通过dispatchMessage方法做的。

Handler.java
...
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) { // 第一种分发方式方式
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) { // 第二种消息分发方式
                return;
            }
        }
        handleMessage(msg); // 第三种消息分发方式
    }
}
...

通过dispatchMessage可以看出共有三种消息分发的方式。callback实现构造handler正是途中标注的第二中。上一小节介绍的handler简单使用,正是对应的标注的第三种消息分发方式。

通过callback构造handler使用示例:

public class HandlerSimpleUseActivity extends AppCompatActivity {
    private TextView handlerTv;
    private Button callback_btn ;


    /**
    *  内部类实现callback
    */
    private Handler handler2 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    handlerTv.setText(msg.obj.toString());
                    break;
                default:
                    break;
            }
            // 重写Handler类的handleMessage会被调用,  true 不会被调用
            return false;
        }
    });



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handler_simple_use_actity);

        handlerBtn = findViewById(R.id.handler_btn);
        handlerTv = findViewById(R.id.handler_tv);
        callback_btn = findViewById(R.id.callback_btn);
        callback_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.obj = "通过callback构造handler";
                        message.what = 1;
                        handler2.sendMessage(message);
                    }
                }).start();
            }
        });
    }
}

  • async
    sync 表示同步,async表示异步。
    aysnc = true表示这是异步消息,为false表示异步消息。默认为异步消息。一般都是异步。关于同步和异步的讲解放在后面。
  • Looper looper
    Looper对象。如果是在主线程调用Looper.getMainLooper方法。传入UI线程创建的Looper。

4.2 handler收发消息

收发消息之前,先介绍一下Message对象,这个封装线程通信数据的实体类。

public final class Message implements Parcelable {
*************************省略代码*************************
private static Message sPool; //Message 的静态缓存类
public int what; //消息的识别标识
public int arg1; //消息的可携带参数
public int arg2; //消息的可携带参数
public Object obj; //消息的可携带参数
Bundle data; //消息的可携带参数
Handler target; //发送消息的Handler实例缓存
long when;  //消息想要传递的时间
Message next; //在跟MessageQueue关联时,指向Queue的上一个节点
Runnable callback; // handler发送消息的时候,传入的Runnable对象

// obtain是从消息池中获取消息对象。类似于线程池,频繁的创建和销毁是有开销的。
// obtain的逻辑,就是消息池如果不为空的话,就从消息池的头部获取一个Message实体。然后消息池的数量减1。如果消息池为null的话,直接创建Message对象实体返回。
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
}     
*************************省略代码*************************
}

收发消息的接口都在Handler里。
下发的思维导图整体做了概括
在这里插入图片描述
前置知识

在说发送和接受消息之前,必须要先解释下,Message中一个很重要的属性:when

when这个变量是Message中的,发送消息的时候,我们一般是不会设置这个属性的,实际上也无法设置,只有内部包才能访问写的操作;将消息加入到消息队列的时候会给发送的消息设置该属性。消息加入消息队列方法:enqueueMessage(…)

在我们使用sendMessage发送消息的时候,实际上也会调用sendMessageDelayed延时发送消息发放,不过此时传入的延时时间会默认为0,来看下延时方法:sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

这地方调用了sendMessageAtTime方法,此处!做了一个时间相加的操作:SystemClock.uptimeMillis() + delayMillis

  • SystemClock.uptimeMillis():这个方法会返回一个毫秒数值,返回的是,打开设备到此刻所消耗的毫秒时间,这很明显是个相对时间刻!
  • delayMillis:就是我们发送的延时毫秒数值
    后面会将这个时间刻赋值给when:when = SystemClock.uptimeMillis() + delayMillis

说明when代表的是开机到现在的一个时间刻 (为什么不获取当前的系统时间,如果使用系统时间,用户修改了系统时间,消息的执行就容易出错,所以使用SystemClock.uptimeMillis()) ,通俗的理解,when可以理解为:现实时间的某个现在或未来的时刻(实际上when是个相对时刻,相对点就是开机的时间点)

4.2.1 发送消息

发送消息主要涉及到俩个方法:post(…)和sendMessage(…)

  1. post(Runnable):发送消息和接收消息都在post中完成
public class Handler{
...

    //post
    public final boolean post(@NonNull Runnable r) {
        // 调用发送延时消息方法。延时默认为0
        return  sendMessageDelayed(getPostMessage(r), 0); 
    }

    // getPostMessage
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r; // m.callback就是一个Runnable对象
        return m;
    }

    // 发送延时消息
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        // SystemClock.uptimeMillis() + delayMillis 计算消息要发送的时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    //发送消息
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 进入队列
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
}

代码演示使用post(Runnable runnable)

public class HandlerSimpleUseActivity extends AppCompatActivity {

    private TextView textView;
    private Handler handler = new Handler();


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handler_simple_use_actity);
        textView = findViewById(R.id.handler_tv);

        findViewById(R.id.post_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // post方法时收发一体的。不用覆写handlerMessage方法
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                textView.setText("post方法");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

为啥能够收发一体往下看:

  1. post参数是一个Runnable对象。post方法里调用getPostMessage(Runnable r)
  2. getPostMessage(Runnable r)方法中。msg.callback = r。
  3. 随后就是进入队列操作。之后loop循环处理消息执行到之前讲过的dispatchMessage方法。这个消息处理三种方式。post发送消息的处理方式就对应dispatchMessage的第一种。
Handler.java
...
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) { // 第一种分发方式方式
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) { // 第二种消息分发方式
                    return;
                }
            }
            handleMessage(msg); // 第三种消息分发方式
        }

    }

	// handleCallback方法
    private static void handleCallback(Message message) {
        message.callback.run();
    }
...

msg.callback 就是post方法传入的Runnable对象。调用handlecallback方法。message.callback.run(),就是Runnable对象的run方法。所以post是收发一体的。没有执行handler的Handlemessage方法。

  1. sendMessage(Message)。
    这个方法就不多说了。

其他的发送消息的方法例如:
在这里插入图片描述
都是post和sendmessage方法的变体。底层调用的都是一样的。

4.2.2 接收消息

接收消息就是dispatchMessage

Handler.java
...
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) { // 第一种分发方式方式
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) { // 第二种消息分发方式
                    return;
                }
            }
            handleMessage(msg); // 第三种消息分发方式
        }

    }
  • handleCallback(msg)
    post方法出发回调,前面介绍过
  • mCallback.handleMessage(msg)
    构造handler对象,实现Handler的Callback回调,返回值为false。使用sendMessage发送消息。前面也介绍过
  • handleMessage(msg)
    最常用。需要重写Handler类的handleMessage方法。使用sendMessage方法发送消息(必须)

4.3 消息进入队列

在这里插入图片描述

上一节中,收发消息中发送消息最后这个消息都会进入队列。消息进入队列源码如下:



Looper
... 

public final class Looper {

    final MessageQueue mQueue; // 全局变量

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
}

Handler
...

public class Handler{

    // 全局变量
    final Looper mLooper;
    final MessageQueue mQueue;

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        // 这个Queue是全局变量Looper中获取到的,代码为mQueue = mLooper.mQueue(是在Handler构造函数中被赋值的)是一个静态变量,也就是MessageQueue 
        // 它对应的是Looper,而不是Handler,这点需要注意,一个Handler可以对应很多Looper,但一个Looper只能有一个Queue

        MessageQueue queue = mQueue; // mQueue是 Looper的MessageQueue
        if (queue == null) { // 如果mQueue为null报错
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {

        // 将发送该Message的Handler保存到自身,到时候当Looper在轮训取出时,获取实例,保证不会出错
        // 所以记住一点每个Message中都带有自身对应的Handler(同步消息除外,下面会讲)
        msg.target = this; 
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) { //是否是异步消息
            msg.setAsynchronous(true);
        }
        // 调用MessageQueue的enqueueMessage方法吧消息放入队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }   
} 

当我们调用 Handler 发送消息的时候,内部其实都会一步一步的走到 sendMessageAtTine() 方法中,如果调用空方法,那么就帮我们创建一个延迟时间为零的 Message 放入到队列中,如果调用其他方法,我们就需要自己创建带有延迟时间和参数的 Message,接着就是无论任何操作,到最后都会调用 enqueueMessage() 这个入队方法,将消息添加到 Queue 中。

MessageQueue 
...

public final class MessageQueue {
    boolean enqueueMessage(Message msg, long when) {

        // 同步消息即平常发送的消息 必修包含handler的 在Handler.enqueueMessage方法中 有msg.target = handler
        // 而同步屏障消息没有msg.target为null 说明同步屏障消息不是这种方法入队列的。
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            // msg 是否已经被使用
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            // 如果Queue已经退出,则将消息recycle 回收的逻辑在下面讲
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                msg.recycle(); //消息回收
                return false;
            }

            // 标记使用状态
            msg.markInUse();
            msg.when = when;
            //mMessage是一个全局变量,代表MessageQueue的头节点 这个比较重要
            Message p = mMessages;

            //头节点为空 或者 等待时间小于头节点  那么将消息放在队列的前端
            if (p == null || when == 0 || when < p.when) {
                // 给msg的上一个节点赋值,这里将头节点设置为下一个,当前msg为头节点
                msg.next = p;
                // 给头节点赋值
                mMessages = msg;
            } else {
                Message prev;
                for (;;) {
                    //比较时间,将消息按照时间插入链表中,就是按照延时时间排队的逻辑
                    prev = p; 
                    p = p.next;
                    if (p == null || when < p.when) { 
                        break;
                    }
                
                }
                //将消息插入合适的位置
                msg.next = p;
                prev.next = msg;
            }
        }
        return true;
    }
}
    

可以看到,逻辑还是相当简单的,就是一个按照时间排序的过程,消息队列是使用 单向链表 作为数据存储的,

这里的排序其实可以分为两种情况

  • 第一种:如果队列中没有消息或者当前进入的消息比消息队列中头部的消息等待时间短,那么就放在消息队列的头部
  • 第二种:反之,循环遍历消息队列,根据when时间值,判断先后顺序,把当前进入的消息放入合适的位置
    发送的消息插入消息队列的的图示如下:
    在这里插入图片描述

4.4 处理消息

处理消息是通过Looper类,之前说Looper管理MessageQueue。通过loop()不断循环从MessageQueue取出消息。

建议先看一下关于为什么在主线程使用Handler没有对主动Looper进行初始化?这个问题。问题的解答在下面。

Looper.loop(): 精简了大量源码,其中主要方法如下:

  • Message msg = queue.next(); 从消息队列中取消息
  • msg.target.dispatchMessage(msg):处理消息
  • msg.recycleUnchecked():消息回收,进入消息池
Looper
...


public final class Looper {
    
    // loop方法
    public static void loop() {
        // 获取Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
    
        // 无限循环 执行逻辑放在loopOnce
        for (;;) {
            // 具体的处理放在loopOnce方法中
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

    // loopOnce方法
    private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {

        // 遍历消息,从MessageQueue中取出消息,可能会阻塞,后面会分析这个方法
        Message msg = me.mQueue.next(); // might block
        if (msg == null) { //没有消息,活命这个MessageQueue队列已经退出了。 当然循环也终止了
            // No message indicates that the message queue is quitting.
            return false;
        }

        try {
            // 分发处理消息
            msg.target.dispatchMessage(msg);
        }

        // 消息回收,进入消息池
        msg.recycleUnchecked();

        return true;
    }
}

首先是判断是否初始化,没有就会抛出异常。然后开始遍历消息。它同样开启的是一个死循环,只要有 Message 传递进来,不管何时,它都会将消息传递出去.,如果为空时,该方法将进入阻塞状态。但是这个阻塞也不会造成 ANR 现象。主要的原因它封装到了Native方法中了。更深层次就不分析了。

遍历消息的关键方法肯定是下面这个

  • Message msg = queue.next():Message类中的next()方法;当然这必须要配合外层for(无限循环)来使用,才能遍历消息队列

来看看这个Message中的next()方法吧

MessageQueue
...

public final class MessageQueue {
    Message next() {
        

        //下个消息需要执行的时间,为延迟消息做准备
        int nextPollTimeoutMillis = 0;
        for (;;) {
            
            //阻塞,除非到了超时时间或者唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                
                // 获取开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 这个if里面的逻辑和同步屏障有关系,目的就是为了找到消息队列中的第一个异步消息。
                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) {
                    if (now < msg.when) {
                        //每个消息处理有耗时时间,之间存在一个时间间隔(when是将要执行的时间点)。
                        //如果当前时刻now还没到执行时刻(when),计算时间差值,传入nativePollOnce定义唤醒阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 达到了执行时刻
                        mBlocked = false;
                        if (prevMsg != null) {
                            // 有同步屏障,这个和同步屏障有关。prevMsg不为空,肯定执行了上面那个和同步屏障相关的if逻辑。
                            // 该操作是把异步消息单独从消息队列里面提出来,然后返回,返回之后,该异步消息就从消息队列里面剔除了
                            prevMsg.next = msg.next;
                        } else {
                            // 无同步屏障,即msg就是头结点,直接获取值,取出msg。把msg的下个消息赋值给mMessages。 mMessage仍处于未分发的同步消息位置
                            mMessages = msg.next;
                        }
                        // 置空
                        msg.next = null;
                        // 标记使用状态
                        msg.markInUse();
                        //返回符合条件的Message
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // 处理完所有挂起的消息后,quit消息
                if (mQuitting) {
                    dispose();
                    //退出队列,同时loop()方法也会结束
                    return null;
                }

                // 后面是idelHandler的操作。下面再讲。   
        }
    }
}

MessageQueue.next是Handler比较核心的地方。里面说到了同步屏障,本小节讲解处理消息可以忽略关于同步屏障的代码,只关注next遍历取出消息的大概逻辑。关于同步屏张后面会讲。但是这里还是说一点吧。

Android 消息可以分为三种

  • 同步消息: 就是handler构造函数参数async值false
  • 异步消息:就是handler构造函数参数async值true
  • 同步屏障消息:这个不是通过enqueueMessage进入队列的,它是通过MessageQueue的postSyncBarrier方法进入队列的。这个消息的msg.target没有赋值的。为null。所以在next方法中。通过msg.target是否为null来判断,此消息是不是同步屏障消息。

通过源码分析画出loop方法消息处理的流程图为
在这里插入图片描述

4.5 消息池

在4.4节 Looper的loop()方法中,msg.recycleUnchecked()是处理完成分发的消息,完成分发的消息并不会被回收掉,而是会进入消息池,等待被复用。

  • recycleUnchecked():回收消息的代码还是蛮简单的,来分析下
    • 首先会将当前已经分发处理的消息,相关属性全部重置,flags也标志可用
    • 消息池的头结点会赋值为当前回收消息的下一节点,当前消息成为消息池头结点
    • 简言之:回收消息插入消息池,当做头结点
    • 需要注意的是:消息池有最大的容量,如果消息池大于等于默认设置的最大容量,将不再接受回收消息入池
      • 默认最大容量为50: MAX_POOL_SIZE = 50
Message.java
...
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++;
        }
    }
}

来看下消息池回收消息图示
在这里插入图片描述

既然有将已使用的消息回收到消息池的操作,那肯定有获取消息池里面消息的方法了

  • obtain():代码很少,来看看
    • 如果消息池不为空:直接取消息池的头结点,被取走头结点的下一节点成为消息池的头结点
    • 如果消息池为空:直接返回新的Message实例
Message.java
...
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

来看下从消息池取一个消息的图示
在这里插入图片描述

4.6 IdleHandler

idle表示闲置的,空闲的。在MessageQueue类中的next方法里,可以发现有关于对IdleHandler的处理,大家可千万别以为它是什么Handler特殊形式之类,这玩意就是一个interface,里面抽象了一个方法,结构非常的简单。IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机。但它执行的时机依赖消息队列的情况,那么如果 MessageQueue 一直有待执行的消息时,IdleHandler 就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。但是慎用,因为可能会拖到很久才执行,如果消息队列里一直有消息

  • IdleHandler代码
MessageQueue.java
...
/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

  • next():精简了大量源码,只保留dleHandler处理的相关逻辑;
MessageQueue
...

public final class MessageQueue {
    Message next() {
        

        // 仅仅在第一次迭代的时候才为-1,因为for循环中最后设置为了0
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        //下个消息需要执行的时间,为延迟消息做准备
        int nextPollTimeoutMillis = 0;
        for (;;) {
            
            //阻塞,除非到了超时时间或者唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 获取开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 这个if里面的逻辑和同步屏障有关系,目的就是为了找到消息队列中的第一个异步消息。
                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());
                }
                // msg 为null 有两种情况 一种是消息队列为null
                // 另一种是 判断同步屏障时,发现消息队列中没有异步消息。msg指向消息对列的末尾。
                if (msg != null) {
                    if (now < msg.when) {
                        //每个消息处理有耗时时间,之间存在一个时间间隔(when是将要执行的时间点)。
                        //如果当前时刻now还没到执行时刻(when),计算时间差值,传入nativePollOnce定义唤醒阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 取消息 返回msg
                    }
                } else {
                    // No more messages.同步屏障取异步消息时没取到也是执行的条件之一
                    // nativePollOnce(ptr, nextPollTimeoutMillis);
                    nextPollTimeoutMillis = -1;
                }

				//这是处理调用IdleHandler的操作,有几个条件
                // 只有第一次的时候才会 pendingIdleHandlerCount= -1  < 0 这个if逻辑才会执行 表明:只会在此for循环里执行一次处理IdleHandler操作
                // (mMessages == null || now < mMessages.when) 当前消息队列为空(mMessages == null) 未到到了可以分发下一消息的时刻(now < mMessages.when)
                if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                    // private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
                    // mIdleHandlers 通过MessageQueue的addIdleHandler(@NonNull IdleHandler handler) 添加值
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                /**
                 * 没有idleHandler ,idleHandler是一个接口嘛 ,我们可以自己实现,最开始的时候 (当前消息队列为空(mMessages == null) 或 未到分发返回消息的时刻)
                 * 去做一些事情。如果没有自定义实现idleHandler ,即没有idleHandler。那么就直接阻塞。mBlocked = TRUE。这也说明了 idleHandler是在handler取消息空闲的时候,
                 * 在这个时间阶段,我们可以自定义做一些事情。
                 */ 
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true; // 这个时间段空闲,你做些事情不,我不做,那就直接阻塞吧。
                    continue;
                }
                // private IdleHandler[] mPendingIdleHandlers; mPendingIdleHandlers 是一个idleHandler数组
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍历idleHandler数据,执行每一个idleHandler的queueIdle()方法
            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 {
                    // queueIdle是idleHandler接口的唯一抽象方法
                    // 这里 queueIdle 的返回值是有意义的,如果返回 false,IdleHandler 被回调后就会被移除,也就是说只会回调一次,如果返回true,每次分发消息的时候(每次调用MessageQueue.next)会多次调用。
                    // 当然 IdleHandler 也是执行在主线程,如果做过重的任务,仍然有可能会阻塞消息队列,导致后面的消息处理不及时
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            // 赋值为0 
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
                
        }
    }
}

idleHandler 在上述代码注释中解释的很清楚了。

  • 怎么使用IdleHandler呢?
    • 这里简单写下用法,可以看看,留个印象
public class MainActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgTv = findViewById(R.id.tv_msg);
        //添加IdleHandler实现类
        mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是IdleHandler"));
        mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是大帅比"));

        //消息收发一体
        new Thread(new Runnable() {
            @Override public void run() {
                String info = "第一种方式";
                mHandler.post(new Runnable() {
                    @Override public void run() {
                        msgTv.setText(info);
                    }
                });
            }
        }).start();
    }

    //实现IdleHandler类
    class InfoIdleHandler implements MessageQueue.IdleHandler {
        private String msg;

        InfoIdleHandler(String msg) {
            this.msg = msg;
        }

        @Override
        public boolean queueIdle() {
            msgTv.setText(msg);
            return false;
        }
    }
}

总结

  • 通俗的讲:当所有消息处理完了 或者 你发送了延迟消息,在这俩种空闲时间里,都满足执行IdleHandler的条件
    • 这地方需要说明下,如果延迟消息时间设置过短的;IdleHandler可能会在发送消息后执行,毕竟运行到next这步也需要一点时间,延迟时间设置长点,你就可以很明显得发现,IdleHandler在延迟的空隙间执行了!
  • 从其源码上,可以看出来,IdlerHandler是在消息分发的空闲时刻,专门用来处理相关事物的

4.7 同步屏障

4.7.1 前置知识

Handler消息类型 可以分为三种

  • 同步消息: 就是handler构造函数参数async值false
  • 异步消息:就是handler构造函数参数async值true
  • 同步屏障消息:这个不是通过enqueueMessage进入队列的,它是通过MessageQueue的postSyncBarrier方法进入队列的。这个消息的msg.target没有赋值的。为null。所以在next方法中。通过msg.target是否为null来判断,此消息是不是同步屏障消息。

4.7.2 同步和异步消息

讲真的,异步消息和同步消息界定,完成是通过一个方法去界定的

  • isAsynchronous():来分析下
    • FLAG_ASYNCHRONOUS = 1 << 1:所以FLAG_ASYNCHRONOUS为2
    • 同步消息:flags为0或者1的时候,isAsynchronous返回false,此时该消息标定为同步消息
      • flags为0,1:同步消息
    • 异步消息:理论上只要按照位操作,右往左,第二位为1的数,isAsynchronous返回true;但是,Message里面基本只使用了:0,1,2,可得出结论
      • flags为2:异步消息
public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}

  • setAsynchronous(boolean async):这个方法会影响flags的值
    • 因为flags是int类型,没有赋初值,故其初始值为0
    • setAsynchronous传入true的话,或等于操作,会将flags数值改成2
msg.setAsynchronous(true);

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

  • 怎么生成异步消息?so easy
Message msg = Message.obtain();
//设置异步消息标记
msg.setAsynchronous(true);

我们正常情况下,很少会使用setAsynchronous方法的,那么在不使用该方法的时候,消息的默认类型是什么呢?

  • 在生成消息,然后发送消息的时候,都会经过下述方法
  • enqueueMessage:正常发送消息(post、延迟和非延迟之类),都会经过此方法
    • 因为发送的所有消息都会经过enqueueMessage方法,然后加入消息队列,可以看见所有的消息都被处理过
    • msg.target = this
      • 这地方给Message类的target赋值了!
      • 说明:只要使用post或sendMessage之类发送消息,其消息就绝不可能是同步屏障消息!
    • 关于同步异步,可以看见和mAsynchronous息息相关
      • 只要mAsynchronous为true的话,我们的消息都会异步消息
      • 只要mAsynchronous为false的话,我们的消息都会同步消息
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

  • mAsynchronous在哪设置的呢?
    • 这是在构造方法里面给mAsynchronous赋值了
public class Handler{

    // 已过时
    @Deprecated 
    public Handler() {
        this(null, false);
    }

    // 已过时 
    @Deprecated
    public Handler(@Nullable Callback callback) {
        this(callback, false);
    }

    // 推荐使用
    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }

    // 推荐使用
    public Handler(@NonNull Looper looper, @Nullable Callback callback) {
        this(looper, callback, false);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}
  • 总结下
    • 这下清楚了!如果不做特殊设置的话:默认消息都是同步消息
    • 不管是同步还是异步消息都会给其target变量赋值:不是同步屏障消息

4.7.3 生成同步屏障消息

在next方法中发现,target为null的消息被称为同步屏障消息,那他为啥叫同步屏障消息呢?

  • postSyncBarrier(long when)
    • sync:同步 barrier:屏障,障碍物 —> 同步屏障
    • 同步屏障实际挺能代表其含义的,它能屏蔽消息队列中后续所有的同步消息
MessageQueue.java
...
@UnsupportedAppUsage
@TestApi
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
        	// prev为null表示while循环没有做事,说明头节点为null。或者同步屏障消息的when比消息队列的头结点的when还要小。
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

  • 同步屏障消息是直接插到消息队列,他没有设置target属性且不经过enqueueMessage方法,故其target属性为null。

总结下:

同步屏障消息插入消息队列的规律,和上面正常发送消息插入基本是一致的;如果消息队列有延时消息,延时消息的时刻大于目前的时刻,同步消息会在这些延时消息之前。
OK,同步屏障消息插入,基本可以理解为:正常的非延时消息插入消息队列!

  • 同步屏障消息插入消息队列流程图
    在这里插入图片描述

4.7.4同步屏障流程

再看看一下next()代码

MessageQueue
...

public final class MessageQueue {
    Message next() {
        

        //下个消息需要执行的时间,为延迟消息做准备
        int nextPollTimeoutMillis = 0;
        for (;;) {
            
            //阻塞,除非到了超时时间或者唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                
                // 获取开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 这个if里面的逻辑和同步屏障有关系,目的就是为了找到消息队列中的第一个异步消息。
                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) {
                    if (now < msg.when) {
                        //每个消息处理有耗时时间,之间存在一个时间间隔(when是将要执行的时间点)。
                        //如果当前时刻now还没到执行时刻(when),计算时间差值,传入nativePollOnce定义唤醒阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 达到了执行时刻
                        mBlocked = false;
                        if (prevMsg != null) {
                            // 有同步屏障,这个和同步屏障有关。prevMsg不为空,肯定执行了上面那个和同步屏障相关的if逻辑。
                            // 该操作是把异步消息单独从消息队列里面提出来,然后返回,返回之后,该异步消息就从消息队列里面剔除了
                            prevMsg.next = msg.next;
                        } else {
                            // 无同步屏障,即msg就是头结点,直接获取值,取出msg。把msg的下个消息赋值给mMessages。 mMessage仍处于未分发的同步消息位置
                            mMessages = msg.next;
                        }
                        // 置空
                        msg.next = null;
                        // 标记使用状态
                        msg.markInUse();
                        //返回符合条件的Message
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // 处理完所有挂起的消息后,quit消息
                if (mQuitting) {
                    dispose();
                    //退出队列,同时loop()方法也会结束
                    return null;
                }

               
        }
    }
}

去掉大量我们无需关注的代码,发现这也没啥嘛,就是一堆if eles for之类的,来​分析分析:

  1. Message msg = mMessages:这步赋值是非常重要的,表示即使我们对msg一顿操作,mMessage还是保留消息队列头结点消息的位置
  2. msg.target == null:遇到同步屏障消息
    1. 首先是一个while循环,内部逻辑,不断将msg节点的位置后移
    2. 结束while的俩个条件
      • msg移到尾结点,也就是移到了消息队列尾结点,将自身赋值为null(尾结点的next)
      • 遇上标记为异步的消息,放行该消息进行后续分发
  3. 分析下,俩个放行条件产生的不同影响
    1. 消息队列不含异步消息
      • 当我们在同步屏障逻辑里面,将msg自身移到尾结点,并赋值为null(尾结点的next)
      • msg为null,是无法进行后续分发操作,会重新进行循环流程
      • mMessage头结点重新将自身位置赋值给msg,继续上述的重复过程
      • 可以发现,上述逻辑确实起到了同步屏障的作用,屏蔽了其所有后续同步消息的分发;只有移除消息队列中的该条同步屏障消息,才能继续进行同步消息的分发
    2. 消息队列含有异步消息
      • 消息队列中如果有异步消息,同步屏障的逻辑会放行异步消息
      • 同步屏障里面堆prevMsg赋值了!请记住在整个方法里面,只有同步屏障逻辑里面对prevMsg赋值了!这个参数为null与否,对消息队列节点影响很大
      • prevMsg为空:会直接将msg的next赋值给mMessage;说明分发完消息后,会直接移除头结点,将头结点的下一节点赋值为头结点
      • prevMsg不为空:不会对mMessage投节点操作;会将分发消息的上一节点的下一节点位置,换成- 分发节点的下一节点,有点绕
      • 通过上面分析,可知;异步消息分发完后,会将其直接从消息队列中移除,头结点位置不变

文字写了一大堆,我也是尽可能详细描述,同步屏障逻辑代码块会产生的影响,整个图,加深下印象!
在这里插入图片描述

4.7.5 同步屏障作用

那么这个同步屏障有什么作用呢?
有个急需的问题,就是什么地方用到了postSyncBarrier(long when)方法,这个方法对外是不暴露的,只有内部包能够调用

加入同步屏障的作用其实就是为了使得当队列中遇到同步屏障时,则会使异步的消息优先执行。比如View的绘制过程

ViewRootImpl
...

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

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

同步消息和异步消息使用建议

在正常的情况,肯定不建议使用异步消息,此处假设一个场景:因为某种需求,你发送了大量的异步消息,由于消息进入消息队列的特殊性,系统发送的异步消息,也只能乖乖的排在你的异步消息后面,假设你的异步消息占据了大量的时间片,甚至占用了几帧,导致系统UI刷新的异步消息无法被及时执行,此时很有可能发生掉帧

4.8 ThreadLocal简单介绍

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

ThreadLocal使用

public class Demo {
    private static ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>();
    public static void main(String[] args) {

        booleanThreadLocal.set(true);
        System.out.println(Thread.currentThread()+":"+booleanThreadLocal.get());

        new Thread("Thread1"){
            @Override
            public void run() {
                booleanThreadLocal.set(false);
                System.out.println(Thread.currentThread()+":"+booleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread2"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread()+":"+booleanThreadLocal.get());
            }
        }.start();
    }
}

在上面的代码中,在主线程中设置 mBooleanThreadLocal 的值为true,在子线程 1 中设置mBooleanThreadLocal的值为false,在子线程2中不设置mBooleanThreadLocal的值。然后分别在3个线程中通过get方法获取mBooleanThreadLocal的值,根据前面对ThreadLocal的描述,这个时候,主线程中应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是nul。运行程序,日志如下所示。

在这里插入图片描述

从上面日志可以看出,虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过 ThreadLocal 获取到的值却是不一样的,这就是 ThreadLocal 的奇妙之处。ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找出对应的 value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

介绍ThreadLocal是为了更好的理解Handler中Looper的存储与获取。

总结

Android 的消息机制在 Java 层及 Native 层均是由 Handler、Looper、MessageQueue 三者构成
在这里插入图片描述

  • Handler:事件的发送及处理者,在构造方法中可以设置其 async,默认为 false。若 async 为 true 则该 Handler 发送的 Message 均为异步消息,有同步屏障的情况下会被优先处理。
  • Looper:一个用于遍历 MessageQueue 的类,每个线程有一个独有的 Looper,它会在所处的线程开启一个死循环,不断从 MessageQueue 中拿出消息,并将其发送给 target 进行处理
  • MessageQueue:用于存储 Message,内部维护了 Message 的链表,每次拿取 Message 时,若该 Message 离真正执行还需要一段时间,会通过 nativePollOnce 进入阻塞状态,避免资源的浪费。若存在消息屏障,则会忽略同步消息优先拿取异步消息,从而实现异步消息的优先消费。

问题分析

1. 为什么handler需要Looper

分析源码,在Handler的构造函数中,最终都会调用`public Handler(@Nullable Callback callback, boolean async) ` 或`public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)`两个构造函数。
public class Handler {
...
    public Handler(@Nullable Callback callback, boolean async) {

        ...
        mLooper = Looper.myLooper(); //如果Looper没有创建会出现空指针异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue; // 如果Looper没有创建会出现空指针异常
        mCallback = callback;
        mAsynchronous = async;
    }
} 

如果没有创建Hander 会出现Handler异常。所以Handler使用之前,要对Looper进行初始化。关于为什么在主线程没有对Looper进行初始化,这个回答见下一个问题。

2. 关于为什么在主线程使用Handler没有对主动Looper进行初始化?

其实主线程是对Looper进行初始化了的。众所周知,在 java 中,程序的入口肯定是一个 main(),而在 Android 中那么它的入口其实同样也是一个 main(),值得注意的是这个方法并不在我们通常使用的界面化窗口 Activity 或者 Application 中,而是在 ActivityThread 这个类里面,它也就是我们常说的 主线程,主要作用就是根据 ActivityManagerService 的要求负责调度四大组件的,这里我们直接看看它的入口 main()方法,如下:
 public static void main(String[] args) {
      *******省略代码*********
      //初始化Looper的方法,同时它会将这个实例赋值到ThreadLocal这个线程本地缓存类中
      Looper.prepareMainLooper();
      //实例化自身,在程序启动是就创建好了
      ActivityThread thread = new ActivityThread();
      //获取ActivityManagerService接口,同时设置ApplicationThread的接口,完成底层通信
      //这里其实就是Activity启动的关键起始位置,但是由于是讲Handler的,只简单提示一下
      //可以对应着Application的attachBaseCotext()方法
      thread.attach(false);
      //这是一个MainHandler,主要是用于管理Activity的生命周期的
      if (sMainThreadHandler == null) {
          sMainThreadHandler = thread.getHandler();
      }
      //开始轮训取出Message
      Looper.loop();
     *******省略代码*********
    }

我们可以看到在 ActivityThread 类中的 main() 中,主线程会先调用 Looper.prepareMainLooper(),完成 Looper 的初始化,然后调用 Looper.loop() 开始轮训 Message。这一段代码很重要,它说明了一个问题,一个为什么我们在主线程中使用 Handler 时不需要启动 Looper 的原因,这是因为 ActivityThread (主线程)已经帮我们是实例化好了。

对Looper中的方法进行分析,补充上面注释内容

Looper
...

//这里是一个final类,不允许被继承,内部必要方法都是通过静态关键字提供出去
public final class Looper {

    //Thread本地缓存类
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    
    // 初始化Looper的方法,同时它会将这个实例赋值到ThreadLocal这个线程本地缓存类中,主线程就是通过这个方法来启动Looper的
    @Deprecated
    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方法 调用prepare(true)
    public static void prepare() {
        prepare(true);
    }

    // 私有的方法,把looper缓存到当前线程中
    private static void prepare(boolean quitAllowed) {
        // 一个线程只能有一个Looper ,否则报错 
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

        //缓存到当前线程中
        sThreadLocal.set(new Looper(quitAllowed));
    }

    // 私有的构造方法
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); //直接创建对应的MessageQueue对象
        mThread = Thread.currentThread();
    }


    // 从本地缓存中获取looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

}

,在主线程中会自动调用 Looper 提供的 prepareMainLooper() ,这个方法主要是 ActivityThread 和 SystemServer 两个类才会调用,然后内部又调用了 prepare(),这里是私有化的,而在 Looper() 内部还有一个同样的命名的方法。

 public static void prepare() {
        prepare(true);
 }

这个方法才是提供给外部调用的(Loop好多方法都是私有的。构造函数也是),相信大家已经看到它两个的区别了,主要是私有化的方法中可以传递一个参数,而提供给外部的没有,而这个 quitAllowed 参数的含义就是当前 MessageQueue 是否可以退出,显而易见,主线程调用时传入的是一个 false,代表的意思是主线程的 MessageQueue 不可以中途退出,而外部所有调用传入的都是一个 true 代表的是子线程的可以中途退出。

紧接着它会从 ThreadLocal 中获取 Looper,如果没有则自己实例化一个。而在 Looper 的构造方法中,我们也可以看到它同步了一个 MessageQueue 对象,一个 Looper 只能对应一个 MessageQueue,而且是在其构造方法时就自动创建好了。

分析的有点多。这也说明了关于为什么在我们在主线程使用Handler没有自己对Looper初始化的原因。

  1. 子线程怎么启动Looper的呢?
    看一下Looper源码

    通过之前的Looper分析。Looper是一个final类,而且构造方法还是私有的。
    Looper暴露出了prepare()方法 来初始化Looper。

    // 对外暴露的方法 prepare方法 调用prepare(true)
    public static void prepare() {
        prepare(true);
    }

    // 私有的方法,把looper缓存到当前线程中
    private static void prepare(boolean quitAllowed) {
        // 一个线程只能有一个Looper ,否则报错 
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        
        //缓存到当前线程中
        sThreadLocal.set(new Looper(quitAllowed));
    }

那子线程使用Handler的流程就是

1. Looper.prepare()
2. 创建Handler
3. Looper.loop()

3.Handler运行在哪个线程里

handler构造函数里面的Looper, 并不一定是handler自己所在的线程构造的,可以是别的线程创造的。Looper就相当于一个工具类。Hander 内部需要把消息发送给这个Looper内部的MessageQueue。Handler中引用的是哪个线程的Looper(即哪个线程创建的Looper),消息的处理就在哪个线程中。因为MessageQueue是在Looper的构造函数里创建的。Looper管理MessageQueue。Handler消息发送消息的时候,是在Handler所在线程发送的,消息加入到MessageQueue的。但是消息的处理的是在MessageQueue中的。MessageQueue是在Looper中创建的,那么Looper在哪个线程,消息的处理的就是在就哪个线程。

4. Handler中主线程的消息队列是否有数量上限?为什么?

Handler主线程的消息队列肯定是有上限的,每个线程只能实例化一个Looper实例(上面讲了,Looper.prepare只能使用一次),不然会抛异常,消息队列是存在Looper()中的,且仅维护一个消息队列

重点:每个线程只能实例化一次Looper()实例、消息队列存在Looper中
一个Handler有一个Looper ,一个Looper但是可以拥有多个Handler。一个Looper拥有一个MessageQueue。

拓展:MessageQueue类,其实都是在维护mMessage,只需要维护这个头结点,就能维护整个消息链表。

5. Handler中有Loop死循环,为什么没有卡死?为什么没有发生ANR?

先说下ANR:5秒内无法响应屏幕触摸事件或键盘输入事件;广播的onReceive()函数时10秒没有处理完成;前台服务20秒内,后台服务在200秒内没有执行完毕;ContentProvider的publish在10s内没进行完。所以大致上Loop死循环和ANR联系不大,问了个正确的废话,所以触发事件后,耗时操作还是要放在子线程处理,handler将数据通讯到主线程,进行相关处理。

线程实质上是一段可运行的代码片,运行完之后,线程就会自动销毁。当然,我们肯定不希望主线程被over,所以整一个死循环让线程保活。

在事件分发里面分析了,在获取消息的next()方法中,如果没有消息,会触发nativePollOnce方法进入线程休眠状态,释放CPU资源,MessageQueue中有个原生方法nativeWake方法,可以解除nativePollOnce的休眠状态。

6. 为什么不建议在子线程中更新UI?

多线程操作,在UI的绘制方法表示这不安全,不稳定。

假设一种场景:我会需要对一个圆进行改变,A线程将圆增大俩倍,B改变圆颜色。A线程增加了圆三分之一体积的时候,B线程此时,读取了圆此时的数据,进行改变颜色的操作;最后的结果,可能会导致,大小颜色都不对。。。

7.子线程和子线程使用Handler进行通信,存在什么弊端?

子线程和子线程使用Handler通信,某个接受消息的子线程肯定使用实例化handler,肯定会有Looper操作,Looper.loop()内部含有一个死循环,会导致线程的代码块无法被执行完,该线程始终存在。

如果在完成通信操作,我们一般可以使用: mHandler.getLooper().quit() 来结束分发操作

  • 说明下:quit()方法会进行几项操作
    • 清空消息队列(未分发的消息,不再分发了)
    • 调用了原生的销毁方法 nativeDestroy(猜测下:可能是一些资源的释放和销毁)
    • 拒绝新消息进入消息队列
    • 它可以起到结束loop()死循环分发消息的操作

拓展:quitSafely() 可以确保所有未完成的事情完成后,再结束消息分发

8.不当的使用Handler,为什么会出现内存泄漏?怎么解决?

内存泄漏

一个对象本应该在它“生命结束”后,被垃圾回收器回收掉。但是由于一个生命周期更长的对象引用了它,导致垃圾回收器无法完成回收。它作为一个“毫无用处”的对象,占用着“无辜”的内存。

造成内存泄漏原因分析

在Activity内将Handler声明成非静态内部类或者匿名内部类,这样Handle默认持有外部类Activity的引用。为什普通内部类支持外部类引用,因为在内部类中可以调用外部类的方法,变量等等,具体细节可以参考普通内部类持有外部类引用的原理。如果Activity在销毁时,Handler还有未执行完或者正在执行的Message,(因为Message.target = handler),而Handler又持有外部类Activity的引用,导致GC无法回收Activity,导致内存泄漏。如以下两种情形可能导致内存泄漏
1、在Activity内将Handler声明成匿名内部类:

   //匿名内部类
   private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

或:

   new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            //大量的操作,activity要销毁时还没结束
        }
    },1000);

2、在Activity内将Handler声明成非静态内部类:

   //非静态内部类
   private class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

    private MyHandler mHandler = new MyHandler();
	

解决方案

1、静态内部类 + 弱引用

在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

   private static class MyHandler extends Handler {
        //弱引用,在垃圾回收时,activity可被回收
        private WeakReference<MainActivity> mWeakReference;

        public MyHandler(MainActivity activity) {
            this.mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

2、如果将Handler声明成可能导致内存泄漏的情况,在Activity销毁时,可清空Handler中未执行或正在执行的Callback以及Message:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //清空handler管道和队列
        mHandler.removeCallbacksAndMessages(null);
    }

参考内容

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值