Handler机制剖析

前言

说起Handler我们再熟悉不过了,Handler用来进行线程间进行通讯的,但是Handler线程间通讯的机制以及原理是什么样的?下面我们就一起来剖析一下。

问题

(1)一个线程可以有几个Handler,可以有几个Looper?
答:一个线程只有一个Looper,可以有多个Handler。
(2)子线程中能否创建子线程的Handler?
答:可以。但是有个前提,在子线程创建Handler,必须先要创建子线程的Looper,可以看Handler的构造函数的源码:

public Handler() {
        this(null, false);
}
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;
    }

这里的Looper.myLooper是先看当前Handler有无对应的Looper,如果没有Looper那么就会抛出异常:Can’t create handler inside thread…that has not called Looper.prepare();
所以,在子线程中创建Handler的前提是先创建对应的Looper,如果没有创建Looper就直接创建Handler,那么就会报异常。
调用了Looper.prepare()就创建了我们的Looper:

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

我们看下这个ThreadLocal是什么?

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

看来,我们在prepare方法中创建Looper之后,并把Looper封装到了ThreadLocal中,我们看一下ThreadLocal类中是怎么存储Looper对象的:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先通过Thread.currentThread()获取到当前的线程,然后,通过当前线程获取当前线程的ThreadLocalMap变量:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

我们看Thread类的源码就可以看到,每一个Thread类都有一个ThreadLocalMap类型的变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

而这里需要注意的一点是这个ThreadLocalMap类是ThreadLocal的一个内部类。
一会咱们再详细介绍ThreadLocalMap这个类,这里咱们先知道,通过Looper.prepare方法,是创建了Looper实例,然后把这个实例存储到了当前线程的的ThreadLocalMap变量中。

Looper中获取Looper实例的方法:

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

所以在子线程中,创建Handler之前,需要先通过Looper.prepare()创建Looper的实例。
在创建完了Handler对象之后,还需要通过Looper.looper()开始轮询,检查MessageQueue中有无消息,如果有消息,就交给Handler来处理消息。所以,在子线程中创建Handler大概是这个样子的:

public class MyThread extends Thread {
    private Handler handler;
    @Override
    public void run() {
        super.run();
        // 创建Looper对象并关联到Thread
        Looper.prepare();
        handler = new Handler(Looper.myLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });
        Looper.loop();
    }
}

这里我们需要注意一下,在创建Looper的时候,会调用Looper的构造函数:

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

我们发现是在Looper的构造函数中创建了MessageQueue。

回顾上面,这个是在子线程中创建Handler,需要调用Looper.prepare和Looper.looper。
那么在主线程中创建Handler的话,需要调用Looper.prepare和Looper.looper。
首先我们需要了解主线程是在什么时候创建的,每个App在启动的时候,都会创建一个进程,同时也会创建一个主线程或者称为UI线程,即我们常提到的ActivityThread。我们一起看一下ActivityThread这个类的main方法:

public static void main(String[] args) {
        .....

        Looper.prepareMainLooper();

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

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

我们发现,这里调用了Looper.prepareMainLooper();方法和Looper.loop()方法。
Looper.loop方法我们已经提到过了,下面我们看一下Looper.prepareMainLooper()这个方法:

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

这里我们发现,Looper.prepareMainLooper方法其实也是调用了Looper.prepare方法,而且我们注意到,这个Looper只能有一个,如果当前线程已经有Looper对象,再创建新的Looper对象就会抛出异常。
所以我们可以判定,不管是在主线程还是子线程,创建Handler对象之前都必须先创建Looper对象,主线程和子线程唯一的不同是,主线程已经在ActivityThread的main方法中为我们提前创建好了Looper对象,并且通过Looper.looper方法开启了轮询。所以我们在主线程中可以直接创建Handler对象。而在子线程中却需要我们提前手动创建Looper对象,并且需要手动通过Looper.loop开启轮询。

下面我们来看一下ThreadLocal中的get方法:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

通过当前线程,获取到Thread中的ThreadLocalMap变量,注意这个ThreadLocalMap类,这个类是ThreadLocal的静态内部类,类似于HashMap,是以this(即当前的ThreadLocal对象)为键,创建的Looper对象为值存储的:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}

Entry继承WeakReference,包括两个元素,一个ThreadLocal<?>类型的成员和一个Object类型的成员value。其中ThreadLocal<?>类型的成员是一个弱引用,其特点是,当引用元素无强引用时,JVM GC时会立即回收引用元素。
Entry对象Key为弱引用,当Key所指对象无强引用时,JVM GC时会自动回收该对象,从而造成Entry状态变为STALE,即无效状态。此时,必须对该Entry对象及其Value引用进行擦除,防止内存泄漏。

下面我们一起来看一下Handler的发送消息:

handler.postAtTime();
handler.postDelayed()
handler.sendMessage();
handler.sendMessageDelayed();
handler.sendEmptyMessage()
handler.sendMessageAtTime()

.........

handler发送消息有好多方法,但是这些方法,最终都调用了同一个方法,那就是:

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

这里调用了enqueueMessage方法:

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

在enqueueMessage中有一行代码格外重要:

msg.target = this;

通过这行代码,message和Handler做了关联。
最终调用了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;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在这里开启了对MessageQueue消息的轮询,这里是消息的入列,根据when,时间先后顺序,时间越早,越先进入消息队列。
这里需要注意的一点:调用了msg.recycle();

public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
 }
 void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

最终调用到了recycleUnchecked方法上,这样做是把消息的属性置空。
上面说的消息入列,下面看一下,消息的出列,消息的出列是通过Looper.loop方法实现的:

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        .......
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            .....
            try {
                msg.target.dispatchMessage(msg);
                .....
            } catch (Exception exception) {
                .....
            } finally {
                .....
            }
            .....
            msg.recycleUnchecked();
        }
    }

由于代码太长,我们只看关键代码,这里通过Looper获取到Looper对应的MessageQueue对象,然后通过一个for循环,调用queue.next()方法不断地从消息队列中拿到Message,拿到之后,就通过msg.target.diapatchMessage(msg);分发消息。这里的msg.target就是我们之前赋值的Handler的对象。dispatchMessage方法:

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

关于Handler的回调:
如果调用了

handler.post(new Runnable() {
         @Override
         public void run() {
                        
         }
})

我们看一下Handler的post方法:

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
}

实际上是通过getPostMessage方法,封装成了一个Message对象,把这个Runnable实例,赋值给了Message的callback对象:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
}

然后会在Handler的dispatchMessage的回调方法中来进行判断:

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

如果callback设置了,那么就会指向handleCallBack方法:

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

这里相当于执行了,这里的run方法:

handler.post(new Runnable() {
         @Override
         public void run() {
                        
         }
})

如果没有设置那么我们就会走:

public void handleMessage(@NonNull Message msg) {
}

handler = new Handler(Looper.myLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
});

关于消息入队和出队轮训的过程可参考下图:
在这里插入图片描述

引申出来的问题:

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

可以看下Looper.loop方法的调用栈:

Looper.loop()
     -> MessageQueue.next()
      -> Message.target.dispatchMessage()
       -> Handler.handleMessage()

可以看出,Handler的handleMessage()所在的线程最终由Looper.loop()的线程决定。
平时我们用的时候,使用在主线程创建的handler从异步线程发送消息到Handler,因为主线程的Looper.loop()方法是在ActivityThread的main方法中调用的,属于在主线程调用,所以对应的这个Handler的handleMessage()方法是在主线程调用的。

2、Handler引起的内存泄漏原因以及解决方案

Handler允许发送延时消息,如果在延时期间用户关闭了Activity,那么该Activity会泄露。这个泄露是因为Message会持有Handler,而又因为Java的特性,内部类会持有外部类的引用,使得Activity会被Handler持有,这样最终导致Activity的泄露。

解决该问题最有效的方法是:将Handler定义成静态内部类,在内部类持有Activity的弱引用,并及时移除所有消息。

示例代码如下:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

并且在Activity.onDestory()前移除消息,加一层保障:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}

这样双重保障就能避免内存泄漏了。

注意:单纯的在onDestroy移除消息并不保险,因为onDestroy并不一定执行。

3、主线程的Looper不允许退出

如果尝试退出主线程的Looper,会得到以下错误信息:

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)

其实原因很简单,主线程不允许退出,退出就意味着APP要挂。

可以看Looper的quit()方法:

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

这里直接调用的MessageQueue的quit方法,再看下MessageQueue的quit方法:

	void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

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

在这里可以看到如果变量 mQuitAllowed 的值为false就会抛出开头说的 "Main thread not allowed to quit."异常。

我们看下mQuitAllowed是在什么时候被赋值的:

	MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

可以看到是在MessageQueue的构造里面赋值的,而MessageQueue又是在Looper的构造中new出来的:

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

Looper又是在Looper.prepare()方法中new出来的:

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

可以看到调用Looper.prepare(boolean quitAllowed)方法有两个地方一个是用户手动调用Looper.prepare(),这里prepare传的参数是true,所以这里不会触发上面提到的那个异常:

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

另一个地方是Looper.prepareMainLooper(),这里写死了false,所以这里是有可能触发上面那个异常的:

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

这个方法一般是系统调用即在ActivityThread的main方法里面调用的,一般我们不手动调用。因为ActivityThread的main方法中调用了Looper.prepareMainLooper()方法就已经创建好主线程的Looper了,这个时候我们再在主线程调用这个方法就会报错,因为一个线程只能创建一个looper否则会报错:The main Looper has already been prepared.

所以可以得出结论,Looper.prepareMainLooper()方法在ActivityThread的main方法中调用(也即在主线程中调用),而Looper.prepareMainLooper()方法中在调用Looper.prepare(boolean quitAllowed)方法时,传了参数值false,所以可以得出结论,主线程的Looper不允许调用Looper的quit方法,否则会报:"Main thread not allowed to quit."异常。

也可以看ActivityThread的main方法,因为Looper.loop执行的是个死循环,所以main方法中Looper.loop();之后调抛出的异常那行代码也就不会执行,但是一旦Looper.loop()停止循环,即quit之后,那么main方法之后的那行代码将会执行。抛出异常:"Main thread loop unexpectedly exited"

	public static void main(String[] args) {
        // Install selective syscall interception
        AndroidOs.install();

       ...省略部分代码

        Looper.prepareMainLooper();

        ...省略部分代码
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
4、Handler里的Callback能干什么?

在Handler的构造方法中有几个要求传入 Callback,那它又是什么,又能做什么呢?

来看看 Handler.dispatchMessage(msg)方法:

public void dispatchMessage(Message msg) {
  //这里的 callback 是 Runnable
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

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

这有什么作用呢?

我们可以利用Callback这个拦截机制来拦截Handler的消息!

场景:Hook ActivityThread.mH,在ActivityThread中有个成员变量 mH,它是一个Handler,又是个及其重要的类,几乎所有的插件化框架都使用了这个方法。

5、创建Message实例的最佳方式

由于Handler极为常用,所以为了节省开销,Android给Message设计了回收机制,所以我们再使用时候尽量复用Message,减少内存消耗。

方法有二:
1、通过Message的静态方法Message.obtain()获取;
2、通过Handler的公有方法handler.obtainMessage();

6、子线程里弹Toast的正确姿势

当我们尝试在子线程里直接去弹Toast的时候,会crash:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

本质上是因为Toast的实现依赖于Handler,按子线程使用Handler的要求修改即可,同理的还有Dialog。

正确示例代码如下:

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

7、我们可以利用Looper的机制来帮助我们做一些事情

1、将Runnable post到主线程执行
2、利用Looper判断当前线程是否是主线程

示例代码如下:

public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}

Android判断是否是主线程的方式:

(1)

Looper.myLooper()==Looper.getMainLooper();

(2)

Looper.getMainLooper().getThread()==Thread.currentThread();

(3)

Looper.getMainLooper().getThread().getId()==Thread.currentThread().getId();
8、Handler中有Looper死循环,为什么没有因为死循环卡死?

(1)Android在启动app的时候,创建主线程ActivityThread,主线程中执行死循环的代码,作用有两个:

  • 保证线程不会死,主线程如果执行结束了,那么app就退出了,所以主线程不能死,就依赖这个死循环。
  • 持续对Message进行接收、处理、发送,保证handler发过来的message都能及时被处理。

(2)既然Handler的消息都是loop来的,为什么没有ANR?
其实产生ANR的问题并不是Looper.loop(),哪怕主线程正在等待(block)。

       for (;;) {
       	Message msg=queue.next();//might block

			...
       }

因为在阻塞的时候,说明主线程在休眠,主线程虽然被block了,但与ANR的问题是没有关系的,只要输入事件有响应,他就不会ANR。ActivityThread线程中的死循环,之所以没有卡死,是借助linux系统的机制,会让主线程在无消息时处于休眠状态,不占用cpu,而在handler发来消息的时候,会唤醒主线程,执行对message的接收、分发,处理完毕后,主线程再次休眠。所以,产生ANR的问题不是因为主线程睡眠了,而是因为输入事件没有响应。

总结

1、Handler的背后有Looper、MessageQueue支撑,Looper负责消息分发,MessageQueue负责消息管理。
2、在创建Handler之前一定需要先创建Looper;
3、Looper有退出的功能,但是主线程的Looper不允许退出。
4、异步线程的Looper需要自己调用Looper.myLooper().quit();退出。
5、Handler.handleMessage()所在的线程是Looper.loop()方法被调用的线程,也可以说成Looper所在的线程。并不一定是创建Handler的线程。
6、使用内部类的方式使用Handler可能会导致内存泄漏,即便在Activity.onDestroy里移除延时消息,必须要写成静态内部类。

参考:https://juejin.cn/post/6844904167534755848

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值