欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android:关于 Handler 消息传递机制(二)

程序员文章站 2022-03-08 14:08:51
...

写在前面

两年前的时候有写过一点对 Handler 的总结(Android:关于 Handler 消息传递机制),现在重新回顾,并增加一些东西和理解。

内容

内存泄露

在 Activity 里我们使用 Handler 的时候,这样写的话 IDE 会提示可能存在内存泄露的问题。

 Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

假设在一个这样的场景,我们通过 Handler 来发送一个延时的消息,在消息还没发送之前,我们就退出了 Activity,那么此时就会存在内存泄露的问题。

原因在于 Handler 在这里是一个匿名内部类,匿名内部类会持有外部类的引用,所以此时 Activity 退出,由于 Handler 里的内容还未处理完,就还持有 Activity 的引用。所以触发 GC 回收的时候,就无法回收这个 Activity。

那么 GC 不回收一些对象是因为GC Root 的可达性,这些对象在 GC Root 上还被引用着,所以就不会被回收。

在使用 Handler 的时候,我们需要Looper.prepare();:

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

 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 类型的 sThreadLocal,是一个静态变量,静态变量就可以作为一个 GC Root 。

那么 Looper 里面会有个 MessageQueue,MessageQueue 里会有 Message:

public final class Looper {
   ...
   final MessageQueue mQueue;
   ...
}

而 Message 的 target 是个 Handler 类型:

public final class Message implements Parcelable {
   ...
   Handler target;
   ...
}

Handler 在发送消息的时候,会把自己设置到这个 target 里:

public class Handler {
       ...
       private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
            msg.target = this;
            ...
            return queue.enqueueMessage(msg, uptimeMillis);
    }
    ...
}

于是这样一条 GC Root 的引用链就出现了:

ThreadLocal --> Looper --> MessageQueue --> Message --> Handler --> Activity

所以 Activity 退出后,因为消息未处理,就导致 Activity 无法被回收,从而导致了内存泄露。

解决方法

清空消息

在 Activity 退出的时候,把消息队列里的消息都清掉

   // MainActivity.java
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);

    }
  
    // Handler.java
    /**
     * Remove any pending posts of callbacks and sent messages whose
     * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
     * all callbacks and messages will be removed.
     */
    public final void removeCallbacksAndMessages(Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }

静态内部类和弱引用

由于静态内部类不会持有外部类 的引用,所以将 Handler 以这种方式呈现,解决到 Handler 与 Activity 的引用关系。

但在一些场景下,我们可能需要在 Handler 里通过持有 Activity 来调用它的一些方法,此时如果直接传入的话,显然就造成了一种直接引用的情况,所以在这种情况下我们需要弱引用,被标记为弱引用的对象,在 GC 回收的时候,就会被回收。因此在被回收的情况下,我们就需要做多一些判空的处理。

所以当我们这个 Handler 里不需要使用到 Activity 的方法的时候,也没必要引入这个 Activity 了,也就不需要多这个弱引用的关系。

private static class MyHandler extends Handler {
        WeakReference<MainActivity> mainActivityWeakReference;

        public MyHandler(MainActivity activity) {
            mainActivityWeakReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = mainActivityWeakReference.get();
            if(activity != null) {
                activity.setTextViewText("hello");
            }
        }
    }

创建一个 Message

我们在创建一个 Message 的时候,可能会使用

Message message = new Message();

但官方里还是推荐我们使用 Handler 的 obtainMessage()方法,它会在一个全局的 Message 池里返回一个新的 Message 给你,从效率上会更快,并且也起到了复用的作用。

 Handler handler = new Handler();
 handler.obtainMessage();

这里面是一个享元的设计模式。

 /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    @NonNull
    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

每个线程只能有一个 Looper

在使用 Handler 的时候,我们需要 Looper.prepare()

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

 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 里会在第一次的时候,创建 Looper,后续再调用的话就会抛出异常,提示每个线程里只能有一个 Looper 被创建。

因此我们要获取当前线程的 Looper 的话,就需要使用 Looper.myLooper()

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal

待续

为什么 Looper 循环不会被阻塞

待续