Handler知识收集整理

我是代码搬运工,不能仅仅只是搬运,还要整理一下。android

1. Handler组成部分:

  1. Message:消息
  2. Handler:消息的发起者
  3. Looper:消息的遍历者
  4. MessageQueue:消息队列

Handler流程图

2. Handler的使用流程:

使用Handler以前的准备工做有三步:面试

  1. 调用Looper.prepare()(主线程不须要调这个,由于APP建立时,main方法里面已经帮咱们建立了)设计模式

  2. 建立Handler对象,重写handleMessage方法(你能够不重写),用于处理message回调的bash

  3. 调用Looper.loop()app

其中:异步

Looper.prepare()的做用主要有如下三点:async

  1. 建立Looper对象ide

  2. 建立MessageQueue对象,并让Looper对象持有oop

  3. 让Looper对象持有当前线程源码分析

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

    private static void prepare(boolean quitAllowed) {
        // 规定了一个线程只有一个Looper,也就是一个线程只能调用一次Looper.prepare()
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 若是当前线程没有Looper,那么就建立一个,存到sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
            
    
复制代码

从上面的代码能够看出,一个线程最多只有一个Looper对象。当没有Looper对象时,去建立一个Looper,并存放到sThreadLocal中

private Looper(boolean quitAllowed) {
        // 建立了MessageQueue,并供Looper持有
        mQueue = new MessageQueue(quitAllowed);
        // 让Looper持有当前线程对象
        mThread = Thread.currentThread();
    }
    
复制代码

这里主要就是建立了消息队列MessageQueue,并让它供Looper持有,由于一个线程最多只有一个Looper对象,因此一个线程最多也只有一个消息队列。而后再把当前线程赋值给mThread。

Handler使用流程:

  1. Handler.post(或sendMessage): handler发送消息msg

  2. MessageQueue.enqueueMessage(msg, uptimeMillis):msg加入message队列

  3. loop() 方法中从MessageQue中取出msg,而后回调handler的dispatchMessage,而后执行callback(若是有的话) 或 handleMessage。(注意,loop方法是一直在循环的,从前面的handler准备工做开始就已经一直在运行了)

如图所示:

ThreadLocal

3. Handler具体源码:

3.1. Message获取

Message的获取方式有两种:

1. Message msg = new Message();

2. Message msg = Message.obtain();

从全局池返回一个新的消息实例。容许咱们在许多状况下避免分配新对象。

    /**
     * Return a new Message instance from the global pool.      Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

复用以前用过的 Message 对象,这里其实是用到了一种享元设计模式,这种设计模式最大的特色就是复用对象,避免重复建立致使的内存浪费

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    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++;
            }
        }
    }
    
每次message使用完了以后会调用recycleUnchecked回收message,方便下次使用

复制代码

Message核心的信息有这些:

public int what;

    public int arg1;

    public int arg2;

    public Object obj;

    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

复制代码
3.2 Handler的发送消息

handler提供的发送消息的方法有不少,大体分为两类:

  1. Handler.post(xxx);
  2. Handler.sendMessage(xxx);

虽然方法不少,但最终都会回调到这个方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

这个方法就是将消息添加到队列里。

3.3 MessageQueue的添加消息

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

			// 标记这个 Message 已经被使用
            msg.markInUse();
            msg.when = when;
            // 这是这个消息队列里的目前第一条待处理的消息(当前消息队列的头部,有可能为空)
            Message p = mMessages;
            boolean needWake;
            // 若是目前队列里没有消息 或 这条消息msg须要当即执行 或 这条消息msg的延迟时间比队列里的第一条待处理的消息还要早的话,走这个逻辑
            if (p == null || when == 0 || when < p.when) {
            	 // 把消息插入到消息队列的头部
                // 最新的消息,若是已经blocked了,须要唤醒
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                
                Message prev;
                
                // 下面这个for循环的做用就是找出msg应该放置的正确位置
                // 通过下面这个for循环,最终会找出msg的前一个消息是prev,后一个消息是p
                
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 上面的for循环得出的结果就是:msg应该在prev后面,在p前面
                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;
    }

复制代码

上面就是handler发送消息过来,而后添加到消息队列里。

下面就开始讲添加消息到队列以后的事情:取消息,而后执行。

3.4 Loop的取消息

前面已经说到,在使用Handler以前有三步准备工做:

  1. 调用Looper.prepare()(主线程不须要调这个,由于APP建立时,main方法里面已经帮咱们建立了)

  2. 建立Handler对象,重写handleMessage方法(你能够不重写),用于处理message回调的

  3. 调用Looper.loop()

其中第三步的Looper.loop()的做用就是不断的从MessageQueue队列里取消息,也就是说,在使用handler发消息以前,就已经开始了loop的循环了

loop()源码比较长,这里摘取核心部分:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     *
     * 大概的翻译就是:在这个线程中运行消息队列。确保调用{@link #quit()}来结束循环。
     *
     */
    public static void loop() {
    
    	····
        ····

        for (;;) {
        	// 不断的从MessageQueue的next方法里取出队列的头部消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ·····
            ·····
            ·····
           
            try {
            	// msg.target是Message在建立时传入的Handler,也就是发送这条消息的发送者handler
            	// 因此最终会回调到handler的dispatchMessage方法
            	
                msg.target.dispatchMessage(msg);
                
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
           
           ····
           ····
			
			// 回收msg,重复利用
            msg.recycleUnchecked();
        }
    }

复制代码

loop()的做用就是不断的从MessageQueue里取消息,而后回调到dispatchMessage里,再看看dispatchMessage里干啥了

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

复制代码

因此最终会执行callback或handleMessage。

4. 面试常见问题

  1. Android中,有哪些是基于Handler来实现通讯的?

答:App的运行、更新UI、AsyncTask、Glide、RxJava等

  1. 处理Handler消息,是在哪一个线程?必定是建立Handler的线程么?

答:建立Handler所使用的Looper所在的线程

  1. 消息是如何插入到MessageQueue中的?

答: 是根据when在MessageQueue中升序排序的,when=开机到如今的毫秒数+延时毫秒数

  1. 当MessageQueue没有消息时,它的next方法是阻塞的,会致使App ANR么?

答:不会致使App的ANR,是Linux的pipe机制保证的,阻塞时,线程挂起;须要时,唤醒线程

  1. 子线程中可使用Toast么?

答:可使用,可是Toast的显示是基于Handler实现的,因此须要先建立Looper,而后调用Looper.loop。

  1. Looper.loop()是死循环,能够中止么?

答:能够中止,Looper提供了quit和quitSafely方法

  1. Handler内存泄露怎么解决?

答: 静态内部类+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息

下面逐个来看看这些问题:

4.1 Android中常见的Handler使用
  1. 保证App的运行

App的入口实际上是ActivityThread的main方法:

public static void main(String[] args) {
        // 建立主线程中的Looper
        Looper.prepareMainLooper();
        
        // 建立ActivityThread对象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            // 建立用于通讯的Handler
            sMainThreadHandler = thread.getHandler();
        }
        // 执行loop方法
        Looper.loop();
    }
    
复制代码

能够看到这个main方法中,主要有以下几步:

  1. 建立了主线程中的Looper,建立Looper时,会建立一个MessageQueue用于存放消息。
  2. 建立了ActivityThread对象,并调用了它的attach方法,这个方法就是去建立Application、调用Application的onCreate方法以及告诉ActivityManagerService如今App启动了。
  3. 建立了用于通讯的Handler,它是一个H对象。
  4. 调用Looper.loop方法,开始循环从主线程中的MessageQueue中取出消息来处理。

回顾下,Handler机制的原理图:

Handler流程图

能够知道,App启动后,由于Looper.loop是一个死循环,致使main方法一直没有执行完,也就是说,咱们后续App中的全部操做,都是发生在Looper.loop中的

那Handler机制,是怎么保证App的运行的呢?咱们来看看ActivityThread中用于通讯的Handler的定义:

private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        // 省略部分代码

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
                    // 省略部分代码
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case PAUSE_ACTIVITY_FINISHING:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&1) != 0);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_SHOW:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_HIDE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                 // 省略部分代码
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }


复制代码

经过代码能够看到,H继承于Handler,定义了不少消息的类型,好比启动Activity、中止Activity、显示Window、电量太低等,它重写了handleMessage方法,用于处理这些消息。

那发消息是在哪里发的呢?咱们以锁屏调用Activity的onStop生命周期为例,其实就是在ApplicationThread中,它是一个ActivityThread的内部类咱们拿启动Activity这个消息举例,ActivityManagerService会先经过binder调用ApplicationThread中的scheduleStopActivity方法(牵涉到进程间通讯,不懂能够略过),这个方法是在咱们App的Bindler线程池中执行的,那看看它是怎么切换到主线程去启动Activity的。

public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

复制代码

咱们看到,这里就是调用sendMessage方法,第一个参数,不就是上面H中定义的消息类型么?接着看 sendMessage方法,它最后会调用到以下这个多参的构造方法:

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

复制代码

能够看到,就是使用mH发送一个消息,而mH就是ActivityThread中定义的类型为H的成员变量,定义以下:

final H mH = new H();
 
复制代码

因此,调用锁屏时,调用Activity的onStop方法,流程以下:

  1. ActivityManagerService经过binder方式,调用ApplicationThread的scheduleStopActivity方法
  2. ApplicationThread的scheduleStopActivity方法,经过H把消息加入到主线线程的MessageQueue中
  3. 主线程的Looper遍历MessageQueue,取到该消息时,调用H的handleMessage方法进行处理
  4. 在H的handleMessage中,调用Activity的onStop方法

流程图以下:

锁屏

那么,咱们App运行原理就出来了,App的运行依赖于Handler机制,当主线程当MessageQueue有消息时,主线程的Looper.loop会不断从主线程中的MessageQueue中取出消息来处理(好比Activity的onCreate其实就是属于对MessageQueue中取出的一个消息的处理),这样就保证了App运行

有消息时

当MessageQueue没有消息时,MessageQueue的next方法会阻赛,致使当前线程挂起,等有消息(通常为系统进程经过binder调用App的ApplicationThread中的方法,注意,方法在binder线程池中执行,而后ApplicationThread使用ActivityThread中的H对象发送消息,加入消息到主线程的MessageQueue中,当发现主线程被挂起了,则会唤醒主线程)

因此,当没有任何消息时,咱们的App的主线程,是属于挂起的状态。有消息来时(锁屏、点击等),主线程会被唤醒,因此说,Handler机制保证了App的运行。

没消息时

4.2 Handler更新UI

咱们知道,若是在子线程直接更新UI会抛出异常,异常以下:

子线程更新UI抛异常

咱们可使用Handler在子线程中更新UI,经常使用的方式有以下几种:

  1. Handler的sendMessage方式
  2. Handler的post方式
  3. Activity的runOnUiThread方法
  4. View的post方式

2.1 Handler的sendMessage方式

final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用sendMessage方式
                Message msg = Message.obtain();
                msg.what = 100;
                handler.sendMessage(msg);
            }
        }).start();
复制代码

这种方式,就是在子线程中,用主线程中建立的hander调用sendMessage发送消息,把Message加入到主线程的MessageQueue中,等主线程的Looper从MessageQueue中取到这个消息时,会调用这个Message的target的handleMessage方法,这个target其实就是咱们发消息用到的handler,也就是调用了咱们重写的handleMessage方法。

发消息:Handler.sendMessage(Message) 处理消息:Message.target.handleMessage(其中target就是发消息的handler)

2.2 Handler的post方法

final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用post
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("handler.post方式");
                    }
                });
            }
        }).start();
复制代码

在子线程中使用handler的post方法,也是能够更新UI的,post方法须要传入一个Runnalbe对象。咱们来看看post方法源码

public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
复制代码

能够看到,先调用了getPostMessage构建了一个Message对象,而后调用了sendMessageDelayed方法,前面知道,sendMessage也是调用的这个方法,因此咱们只要关注怎么构建的Message对象,看getPostMessage方法。

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        // post方法传入的Runnable对象做为Message的callback
        m.callback = r;
        return m;
    }
复制代码

其实很简单,就是把post方法传入的参数做为Message的callback来建立一个Message。

咱们再来回顾一下从MessageQueue中取出消息来对消息对处理,方法是Handler对dispatchMessage方法

public void dispatchMessage(Message msg) {
        // callback其实就是post方法传入对Runnable对象
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 不会执行到这里
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        // 至关于调用了post方法传入对Runnable对象对run方法
        message.callback.run();
    }
复制代码

能够知道,咱们使用post发送消息,就是使用传入对Runnable对象封装成一个Message,而后加入到主线程中的MessageQueue,等主线程的Looper取出该消息处理时,由于Message.callback不为空,而调用其run方法,也就是咱们调用post方法传入的Runnable对象的run方法,且不会调用Hander的handleMessage方法。

发送消息:Handler.post(Runnable) 处理消息:Message.callback.run(callback为调用post方法传入的Runnable)

2.3 Activity的runOnUiThread方法

new Thread(new Runnable() {
            @Override
            public void run() {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("Activity的runOnUiThread方法");
                    }
                });
            }
        }).start();
复制代码

咱们若是能在子线程中获取到Activity对象,是能够调用其runOnUiThread方法,来更新UI。咱们来看看Activity的runOnUiThread源码。

public final void runOnUiThread(Runnable action) {
        // 若是不是UI线程,则调用Handler的post
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            // 若是是ui线程,则直接回调Runnable的run方法
            action.run();
        }
    }
复制代码

若是是UI线程,就直接调用了传入的Runnable对象的run方法,咱们主要是看非UI线程的逻辑。

若是不是UI线程,则直接使用mHandler的post方法,看来Activity的runOnUiThread方法也是基于Handler的post方法来实现的,后面的逻辑就是把传入的Runnable封装成Message发送出去,上面讲过了,就再也不复述了。

咱们再看看这个mHandler的定义,其实就是Activity的成员属性

final Handler mHandler = new Handler();
复制代码

Activity是在主线程建立的,因此这个Handler也是在主线程中建立的,且持有的Looper为主线程的Looper。那么使用这个Handler调用post方法发出的消息,是加入到主线程的MessageQueue中,这样就完成了子线程跟主线程的通讯。

发送消息:Activity. runOnUiThread(Runnable) 处理消息:Message.callback.run(callback为runOnUiThread方法传入的Runnable)

2.4 View的post方法

new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用View的post方法
                btn.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("View.post方式");
                    }
                });
            }
        }).start();
复制代码

咱们直接看View的post方法

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }
复制代码

能够看到这里也是调用的Handler的post方法,跟Activity. runOnUiThread相似。

发送消息:View.post(Runnable) 处理消息:Message.callback.run(callback为post方法传入的Runnable对象)

总结一下:

  1. Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.target.handleMessage方法
  2. Handler.post: 基于Handler.sendMessage,把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.callback.run方法
  3. Activity.runOnUiThread: 基于Handler.post
  4. View.post: 基于Handler.post

因此,以上子线程更新主线程UI的全部方式,都是依赖于Handler机制。

  1. AsyncTask

当咱们想在子线程中作耗时任务时,会考虑使用AsyncTask,咱们来举个栗子,在子线程中去建立自定义的MyAsyncTask并执行它,在doInBackground中去模拟耗时操做:

public class AsyncTaskActivity extends AppCompatActivity {
    private static final String TAG = "AsyncTaskActivity";
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        btn = (Button) findViewById(R.id.btn);
        // 开启一个子线程,去执行异步任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run, thread name:"  + Thread.currentThread().getName());
                MyAsyncTask asyncTask = new MyAsyncTask();
                asyncTask.execute();
            }
        }).start();
    }

    // 自定义AsyncTask,并重写相关方法,并打印执行所在线程
    class MyAsyncTask extends AsyncTask<Void, Integer, Void>{

        @Override
        protected Void doInBackground(Void... voids) {
            Log.e(TAG, "doInBackground, thread name:"  + Thread.currentThread().getName());
            // 模拟耗时任务
            for (int i = 0; i < 5; i ++) {
                SystemClock.sleep(1000);
                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            Log.e(TAG, "onPreExecute, thread name:"  + Thread.currentThread().getName());
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            Log.e(TAG, "onPostExecute, thread name:"  + Thread.currentThread().getName());
            btn.setText("执行完了!");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.e(TAG, "onProgressUpdate, thread name:"  + Thread.currentThread().getName());
        }
    }
}
复制代码

AsyncTask_log
能够看到,咱们建立的子线程名为Thread-4,而AsyncTask的方法所在线程以下:

  1. onPreExecute: Thread-4,其实就是调用AsyncTask的execute方法的线程
  2. doInBackground: AsyncTask #1,这实际上是AsyncTask的线程池中的一个线程
  3. onProgressUpdate: main,即主线程
  4. onPostExecute: main,即主线程

关于onPreExecute,这很好实现,不须要切换线程,直接回调就能够;而doInBackground方法的执行,能够直接取AsyncTask维持的线程池来执行就能够。咱们重点关注onProgressUpdate和onPostExecute方法,是怎么从子线程切换到主线程的。

咱们从AsyncTask的源码中能够看到这样一个内部类

private static class InternalHandler extends Handler {
        public InternalHandler() {
            // 使用主线程的Looper去建立Handler
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // finish中主要调用onPostExecute
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    // 调用onProgressUpdate
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
复制代码

从handleMessage方法看到,这里切换到主线程,也是使用的Handler机制来实现的,可是为何咱们无论在任何线程建立的AsyncTask去执行,最后均可以在主线程去回调这两个方法呢?主要是建立InternalHandler时,使用的是主线程的Looper,这样使用这个InternalHandler发送消息时,消息就会加入到主线程Looper对应的MessageQueue中,因此主线程Looper取出消息处理时,InternalHandler的handleMessage方法就是在主线程中回调的了。

因此AsyncTask其实就是基于线程池+Handler机制来实现的。

  1. 其余使用Handler的地方

  2. RxJava: 子线程切换到主线程执行观察者的回调方法(RxJava我不熟悉)

  3. Glide:图片准备好之后的回显

  4. LocalBroadcastManager: 传统的广播是基于binder实现的,本地广播LocalBroadcastManager是基于Handler实现的

其实还有不少使用到handler机制的地方,就不一一举例了,反正记住,Handler机制很重要。

4.3 处理Handler消息,是在哪一个线程?必定是建立Handler的线程么?

之前总以为,处理消息的线程,就是建立Handler的线程,可是上一篇文章的分析,咱们知道这样说实际上是不许确的(由于咱们建立Handler一般使用的默认Looper)。

处理消息的线程,实际上是发送handler所持有的Looper所在的线程。

其实原理很好理解,咱们知道Handler的原理如图

Handler流程图
因此,消息的处理分发所在线程彻底取决于消息所在MessageQueue的线程,若是想要在某个线程中处理消息,只要作到把消息加入到那个线程所对应的MessageQueue中。

就像上面讲到AsyncTask的例子,就算咱们在子线程建立了AsyncTask(即在子线程建立了用于通讯的Handler),但只要咱们建立Handler的时候,经过Looper.getMainLooper()传入主线程的Looper ,那么消息就加入到了主线程所对应的MessageQueue中,消息就是在主线程中处理的。

以下,咱们在子线程中建立一个handler,而后在主线程发送消息,由于建立handler使用的是子线程中的Looper,因此消息是在主线程中处理的。代码以下:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 开启一个子线程,去建立Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送一个消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }

}
复制代码

log输出,能够看出,处理消息是在子线程中:

log
按照以前的说法,若是咱们想在主线程中处理消息,只要把消息加入到主线程的MessageQueue中,因此咱们能够建立Looper时,传入主线程的Looper,代码以下:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 开启一个子线程,去建立Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                // 建立Handler传入主线程的Looper
                mHandler = new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送一个消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }
}
复制代码

能够看到,建立Handler使用了主线程的Looper后,的确消息是在主线程中处理的了:

log

4.4 是如何插入到MessageQueue中?

咱们以前说,全部handler.post和handler.sendMessage都会调用到Handler的sendMessageDelayed方法,方法以下:

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

这里逻辑就很简单了,直接调用了sendMessageAtTime方法,第一个参数为Message,第二个参数为SystemClock.uptimeMillis() + delayMillis,其中delayMillis为延时的时间,单位为毫秒,SystemClock.uptimeMillis() 为开机到如今的时间(不包括休眠时间),单位为毫秒。第二个参数主要是决定该Message在MessageQueue的顺序,好比如今开机时间为100s,发送一个延时20s的消息,则二者之和为120s; 过了5秒,又发了一个延时5s的消息,则二者只喝为105+5 = 110s。

sendMessageAtTime最后会调用到MessageQueue的enqueueMessage方法,咱们来看看这个方法:

boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            //  when = 开机到目前的时间 + 延时时间
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 若是当前消息队列为空或者当前when小于队头when
            // 则把消息插入到队头
            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;
                // 死循环,根据when寻找Message插入到MessageQueue合适的位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // MessageQueue中依次日后找到第一个Message.when大于当前Message.when的Message
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把当前Message插入到MessageQueue的合适位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                // 若是须要唤醒,则调用nativeWake去唤醒处理线程
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

从上面的代码,能够很容易看出,Message在MessageQueue中是根据when从小到大来排队的,when是开机到如今的时间+延时时间。

好比,咱们假设开机时间为100s,此时MessageQueue没有消息,这时候发一个延时20s的消息,即when为120000,则MessageQueue中的消息状况如图:

MessageQueue开始
过了5s,咱们又发了一个延时10s的消息,则when为115000,此时MessageQueue如图:

MessageQueue开始过了5s
又过了5s,咱们发了一个不延时的消息,即when为110000,此时MessageQueue如图:

MessageQueue又过了5s
因此,Message在MessageQueue中是根据when从小到大来排队的,when是开机到如今的时间+延时时间。

4.5 MessageQueue的next会形成App的ANR么

咱们知道Activity若是5s的事件都不能相应用户的请求,则会ANR。咱们在来回顾下Looper.loop方法:

public static void loop() {
        final Looper me = myLooper();
        // 取到当前线程的MessageQueue
        final MessageQueue queue = me.mQueue;
        
        // 死循环
        for (;;) {
            // 调用MessageQueue.next,从队列中取出消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
            // 对消息对分发
            msg.target.dispatchMessage(msg);

          // 省略无关代码
        }
    }
复制代码

在前面讲过Looper.loop方法维持了App的运行,它是里面使用了一个死循环,咱们App日常的操做(Activity的生命周期、点击事件等)都是属于调用了 msg.target.dispatchMessage(msg)对消息的处理。可是若是MessageQueue中没有消息时,MessageQueue的next方法会阻塞,那它会致使App对ANR么?

咱们来看看MessageQueue的next方法

Message next() {

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        // 死循环
        for (;;) {
            // 阻塞MessageQueue
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                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) {
                    if (now < msg.when) {
                        // 若是是延时消息,则算出须要阻塞的时间nextPollTimeoutMillis
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 若是不是延时显示,则直接把消息返回,以供处理
                        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 {
                    // 没有消息时,设置nextPollTimeoutMillis为-1,阻塞MessageQueue
                    nextPollTimeoutMillis = -1;
                }
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 通常都会知足if条件,而后mBlocked设置为true,继续continue
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                //  省略无关代码
            }

            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0;
        }
    }

复制代码

next方法中,首先设置一个死循环,而后调用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一个native方法,用于阻塞MessageQueue,主要是关注它的第二个参数nextPollTimeoutMillis,有以下三种可能:

  1. 若是nextPollTimeoutMillis=-1,一直阻塞不会超时。
  2. 若是nextPollTimeoutMillis=0,不会阻塞,当即返回。
  3. 若是nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),若是其间有程序唤醒会当即返回。

咱们先继续往下看,开始nextPollTimeoutMillis为0,也就是不会阻塞,则继续往下,这时候有三种状况

  1. 延时消息,则直接算出目前须要延时的时间nextPollTimeoutMillis,注意,这时候并无把消息返回,而是继续往下,设置mBlocked为true,表示消息队列已阻塞,并continue执行for循环体,再次执行nativePollOnce方法,这时候nextPollTimeoutMillis>0,则会致使MessageQueue休眠nextPollTimeoutMillis毫秒,接着应该会走到状况2.
  2. 不是延时消息,则设置mBlocked为false,表示消息队列没有阻塞,直接把消息返回,且把消息出队。
  3. 若是消息为空,则调用位置nextPollTimeoutMillis为-1,继续往下,设置mBlocked为true,表示消息队列已阻塞,并continue继续for循环,这时候调用nativePollOnce会一直阻塞,且不会超时。

因此,当消息队列为空时,实际上是调用本地方法nativePollOnce,且第二个参数为-1,它会致使当前线程阻塞,且不会超时,因此不会出现ANR的状况,其实这是由Linux的管道机制(pipe)来保证的,当线程阻塞时,对CPU等资源等消耗时极低的,具体的原理能够自行查阅。

那线程阻塞之后,何时才能再唤醒呢?记得以前咱们说消息加入MessageQueue的逻辑么?咱们再来回顾一下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;
                // 若是MessageQueue为空
                needWake = mBlocked;
            } else {
                // 若是MessageQueue中有消息
                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;
            }

            // 若是须要唤醒当前线程,则调用nativeWake方法
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

其实就是使用Handler发送消息,把消息加入到MessageQueue时,会判断当前MessageQueue是否阻塞,若是阻塞了,则须要调用nativeWake方法去唤醒线程,而这个阻塞是在前面提到的MessageQueue的next方法中,MessageQueue没有消息或者消息为延时消息时设置的。 因此MessageQueue的next方法可能会阻塞线程,但不会形成ANR。

  1. 当MessageQueue没有消息时,next方法中调用nativePollOnce致使线程阻塞,直到有新消息加入MesssageQueue时调用nativeWake来唤醒线程
  2. 当MessageQueue有消息时且队头消息为延时消息时,next方法调用nativePollOnce致使线程阻塞nextPollTimeoutMillis的时间,中途有新消息加入MessageQueue时调用nativeWake能够唤醒线程,也能够等nextPollTimeoutMillis后自动唤醒线程
4.6 子线程使用Toast

有时候,咱们须要在子线程中直接弹出Toast来提示一些信息,代码以下:

public class ToastActivity extends AppCompatActivity {
    private static final String TAG = "ToastActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toast);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程中弹出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
            }
        }).start();
    }
}
复制代码

运行程序,会发现程序会崩溃。是由于子线程不能更新UI么?其实不是不这样的。

  1. Toast是属于系统Window,不受子线程更新UI限制。
  2. onCreate方法中,子线程可能能够更新UI,由于子线程不能更新UI的检测是在ViewRootImpl的checkThread完成的,而onCreate方法中,ViewRootImpl尚未建立,因此不会去检测。

既然不是这两方面的缘由,咱们来看看报错的log吧

log
这跟咱们在子线程中直接使用handler好像报的错误相似,那咱们也跟使用hendler的套路同样,先调用Looper.prepare而后再调用Looper.loop呢?代码以下:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                // 子线程中弹出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();

复制代码

发现就能够了,能够看出Toast也是须要使用Handler,咱们来看看Toast的实现,直接看Toast中的一个内部类TN,它是一个IBinder实现类,咱们来看它的定义:

private static class TN extends ITransientNotification.Stub {
       final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                // 调用handleShow,处理显示逻辑
                handleShow();
            }
        }
        final Handler mHandler = new Handler();    

        // 省略无关代码

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            // 这里是Binder线程池,用handler切换到有Looper的线程
            mHandler.post(mShow);
        }
        
        // Toast的真实显示
        public void handleShow() {
            if (mView != mNextView) {
                mView = mNextView;

                // 省略无关代码

                // 把View加入到Window上
                mWM.addView(mView, mParams);
            }
        }
        
        //省略无关代码
}
复制代码

能够看出,Toast的显示,使用了Binder通讯,其实就是WindowManagerService会拿到TN对象,调用其show方法,可是这是Binder线程池中执行的,因此使用handler切换到调用Toast的show方法所在的线程去执行,这里使用的就是handler.post,因此就须要调用Toast.show方法所在线程有Looper。最后调用的就是handleShow方法,把View加载到Window上。

总结一下Handler:

  1. Toast是系统Window来实现的
  2. Toast的显示使用了IPC
  3. Toast的显示使用了Handler机制
  4. 子线程可使用Toast,不过须要使用Handler的套路
4.7 Looper.loop能够中止么?

前面咱们知道,loop方法中是一个死循环,又由于代码是顺序执行的,因此它以后的代码是得不到执行的,以下:

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 开启一个子线程,去执行异步任务
        new Thread(new Runnable() {
            @Override
            public void run() {
               
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                 Log.e(TAG, "Looper.loop以前" );
                // Looper.loop方法是一个死循环
                Looper.loop();
                // 得不到执行
                Log.e(TAG, "Looper.loop以后" );
            }
        }).start();
    }
}
复制代码

log如图,只会打印loop方法以前的,loop以后的代码得不到执行:

log
这样咱们就要考虑一个问题了,并非全部线程都须要像主线程同样一直运行下去,有些线程但愿作完耗时任务后能回收,可是由于Looper.loop方法,致使线程只是阻塞,随时有被唤醒的可能,不能释放。那有什么办法能中止loop方法么?

其实Looper提供了quit和quitSafely方法来中止Looper,咱们先来看看quit的用法,在点击事件中调用了Looper的quit方法,修改后的代码以下

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 开启一个子线程,去执行异步任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                Log.e(TAG, "Looper.loop以前" );
                // Looper.loop方法是一个死循环
                Looper.loop();
                // 得不到执行
                Log.e(TAG, "Looper.loop以后" );
            }
        }).start();
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 调用Looper的quit方法,中止Looper
                mHandler.getLooper().quit();
            }
        });
    }
}
复制代码

开始和以前同样,Looper.loop后的方法不会获得执行,咱们点击按钮后,Looper会中止,Looper.loop以后的代码也能够获得执行,log以下:

log
咱们来看看Looper的quit和quitSafely的源码:

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

    public void quitSafely() {
        mQueue.quit(true);
    }
复制代码

咱们发现,两个方法都是调用了MessageQueue的quit方法,只是传入的参数不一样,咱们来看看MessageQueue的quit方法:

void quit(boolean safe) {
        synchronized (this) {
            if (mQuitting) {
                return;
            }

            // MessageQueue正在中止,用于next方法退出死循环
            mQuitting = true;

            if (safe) {
                // 删除MessageQueue中的延时消息
                removeAllFutureMessagesLocked();
            } else {
                // 删除MessageQueue中的全部消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
复制代码

首先把mQuitting设置为true,主要用于MessageQueue的next方法退出死循环,而后经过safe去判断逻辑逻辑,这里就能够看出Looper的quit和quitSafely的区别了

  1. quit: 删除MesageQueue中全部消息
  2. quitSafely: 删除MessageQueue中的延时消息

咱们继续看mQuitting对MessageQueue的next方法的影响,回到next方法,咱们只看关键性代码:

Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
              
                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) {
                        // 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 {
                        // 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;
                }

                // 判断mQuitting
                if (mQuitting) {
                    dispose();
                    return null;
                }

            }

        }
    }
复制代码

直接看最后的部分,对mQuitting作判断,咱们以前在MessageQueue的quit方法中,会把这个属性设置为true,其实就是会影响到这里。知足条件之后,调用了dispose方法,并返回了null。

咱们先来看dispose方法

private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }
复制代码

其实就是调用了nativeDestroy方法,它是一个native方法,用于在底层中止MessageQueue。

这里只是中止了MessageQueue的next中的死循环,Looper.loop方法中的死循环仍是没有退出,咱们继续看Looper.loop方法。

public static void loop() {
        final Looper me = myLooper();
       
        final MessageQueue queue = me.mQueue;

        for (;;) {
            // 当mQuitting为true,queue.next方法返回了null
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 直接return,退出loop的死循环
                return;
            }
            // 省略无关代码
        }
    }

复制代码

前面咱们知道,当调用了Looper的quit或者quitSafely时,会设置当前线程的MessageQueue的 mQuitting为true,而后致使了MessageQueue的next返回了null,而后直接return了,退出了loop中的死循环,这样就完成了中止Looper的逻辑。

4.8 Handler的内存泄漏

咱们一般会使用以下的方式去使用handler来通讯

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

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

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送延时100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 使用leakcanary作内存泄漏检测
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        if (refWatcher != null) {
            refWatcher.watch(this);
        }
    }
}
复制代码

可是会有一个问题,咱们进入这个页面而后点击按钮,发送一个延时100s的消息,再退出这个Activity,这时候可能致使内存泄漏。

根本缘由是由于咱们建立的匿名内部类Handler对象持有了外部类Activity的对象,咱们知道,当使用handler发送消息时,会把handler做为Message的target保存到MessageQueue,因为延时了100s,因此这个Message暂时没有获得处理,这时候它们的引用关系为MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,以下图所示

Handler内存泄漏
当退出这个Activity时,由于Handler还持有Activity,因此gc时不能回收该Activity,致使了内存泄漏,使用LeakCanary检测,效果以下图所示:

LeakCanary-泄漏图
固然,过了100s,延时消息获得了处理,Activity对象属于不可达的状态时,会被回收。

那怎么来解决Handler泄漏呢?主要有以下两种方式:

  1. 静态内部类+弱引用
  2. 移除MessageQueue中的消息
4.8.1 静态内部类+弱引用

咱们知道,静态内部类是不会引用外部类的对象的,可是既然静态内部类对象没有持有外部类的对象,那么咱们怎么去调用外部类Activity的方法呢?答案是使用弱引用。代码以下:

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        
        // 建立Handler对象,把Activity对象传入
        mHandler = new MyHandler(HandlerActivity.this);

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送延时100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    // 静态内部类
    static class MyHandler extends Handler {
        private WeakReference<Activity> activityWeakReference;
        public  MyHandler(Activity activity) {
            activityWeakReference = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 处理消息
            if (activityWeakReference != null) {
                Activity activity = activityWeakReference.get();
                // 拿到activity对象之后,调用activity的方法
                if (activity != null) {

                }
            }
        }
    }
}
复制代码

首先,咱们自定义了一个静态内部类MyHandler,而后建立MyHandler对象时传入当前Activity的对象,供Hander以弱应用的方式持有,这个时候Activity就被强引用和弱引用两种方式引用了,咱们继续发起一个延时100s的消息,而后退出当前Activity,这个时候Activity的强引用就不存在了,只存在弱引用,gc运行时会回收掉只有弱引用的Activity,这样就不会形成内存泄漏了。

但这个延时消息仍是存在于MessageQueue中,获得这个Message被取出时,仍是会进行分发处理,只是这时候Activity被回收掉了,activity为null,不能再继续调用Activity的方法了。因此,其实这是Activity能够被回收了,而Handler、Message都不能被回收。

至于为何使用弱引用而没有使用软引用,其实很简单,对比下二者回收前提条件就清楚了

  1. 弱引用(WeakReference): gc运行时,不管内存是否充足,只有弱引用的对象就会被回收
  2. 软引用(SoftReference): gc运行时,只有内存不足时,只有软引用的对象就会被回收

很明显,当咱们Activity退出时,咱们但愿无论内存是否足够,都应该回收Activity对象,因此使用弱引用合适。

4.8.2 移除MessageQueue中的消息

咱们知道,内存泄漏的源头是MessageQueue持有的Message持有了Handler持有了Activity,那咱们在合适的地方把Message从MessageQueue中移除,不就能够解决内存泄漏了么?

Handler为咱们提供了removeCallbacksAndMessages等方法用于移除消息,好比,在Activity的onDestroy中调用Handler的removeCallbacksAndMessages,代码以下:

@Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除MessageQueue中target为该mHandler的Message
        mHandler.removeCallbacksAndMessages(null);
    }
复制代码

其实就是在Activity的onDestroy方法中调用mHandler.removeCallbacksAndMessages(null),这样就移除了MessageQueue中target为该mHandler的Message,由于MessageQueue没有引用该Handler发送的Message了,因此当Activity退出时,Message、Handler、Activity都是可回收的了,这样就能解决内存泄漏的问题了。

5. 结尾

Handler的知识很少,但细节特别多,一旦久一点没看就会忘记。 因此,无论是别人写的仍是本身写的,先把相关知识记下来,下次忘记了回来再看一下就好了。

6. 参考文章

  1. www.jianshu.com/p/592fb6bb6…
  2. www.jianshu.com/p/9631eebad…
  3. www.jianshu.com/p/67eb02c8b…
相关文章
相关标签/搜索