安卓handler通信机制相关的几个问题

1. 为什么在activity中直接new一个handler的匿名内部类会有可能发生内存泄漏

scala 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                System.out.println("zz");
            }
        };
        
    }
}

首先明确一点,在一个方法内部new一个匿名内部类的时候,这个匿名内部类会持有外部类的引用。 因此这个handler会持有activity这个对象引用。handler是被message所有的,可以看如下源码

ini 复制代码
public static Message obtain(Handler h, int what, int arg1, int arg2) {
    Message m = obtain();
    m.target = h;
    m.what = what;
    m.arg1 = arg1;
    m.arg2 = arg2;

    return m;
}

在这个源码中可以看出,这个message在获得的时候应该是和要处理的handler绑定的。也就是说message中已经知道了谁要去处理这个message。或者这样说更具体一点,在发送端发送这个message的时候呢,要进入共享的消息队列,这些都后面再详细说。

less 复制代码
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);
}

因此可以看出这个message会和发送的这个handler进行绑定,相当于这一个handler要在主线程中进行处理,同时在子线程中作为一个成员变量进行发送!总之可以明确这个handler是和message绑定的。 同时可以看出,message是和messageQueue进行绑定的,message是messageQueue的一个属性。接下来看这个messageQueue,在looper这个类内部有一个messageQueue这个属性

ini 复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

在代码中可以看出,这个mQueue在loope的构造方法中被创建。

csharp 复制代码
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被作为一个对象放到了sThreadLocal属性内部。同时看出这个sThreadLocal是一个静态变量。因此找到了GCRoot.因此,当这个message长期得不到处理的时候,就会发生内存泄漏。

2. 一个looper存在多个handler,如何保证线程安全的。

这个问题可以看一下handler整体的处理流程,在handler进行sendMessage操作的时候,会将这个messsage放入到messagequeue中

ini 复制代码
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        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;

以上仅仅是复制了部分源码,可以看出在代码中引入了synchroized锁,目的是保证在message进入这个共享队列的时候的线程安全性。 在取出消息的时候呢?

java 复制代码
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        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;

可以看上述代码,同时在取出的操作中也加入了synchroized锁。因此,这也是线程安全的。目的是不希望,插入队列和取出消息这两个操作产生冲突。

3.handler整个消息体制的一个运行过程

首先看最初始的源码,ActivityThread.java中的main方法,

ini 复制代码
Looper.prepareMainLooper();

准备好looper,并创建好消息队列

ini 复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

之后

ini 复制代码
Looper.loop();

在这个方法内部进入一个for(;;)循环开始不断尝试进行取消息。因此,可以在主线程new一个handler,重写这个handlemessage方法。同时把这个handler放入到子线程中sendmessage进行跨线程通信。 同时应该明确的是看如下代码

less 复制代码
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到同一个looper共享同一个messageQueue.而looper是和当前线程进行绑定的。看如下代码

ini 复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

因此只要将一个handler仍到���同的线程里,就可以实现多线程之间的通信。

3.为什么new HandlerThread()默认是和主线程进行通信。看源码!

ini 复制代码
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
csharp 复制代码
public static void prepare() {
    prepare(true);
}
csharp 复制代码
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));
}
ini 复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

关键之处就是在于looper会个当前的thread通过sThreadLocal进行绑定。

4.Message如何获取,handler是如何利用handler的。 在next()获取到消息后,

ini 复制代码
msg.recycleUnchecked();

会执行以上方法。在这个方法内部只要是将message的所有属性都清空。

ini 复制代码
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++;
        }
    }
}

同时将清空后的message加入到缓存队列sPool中。 当取消息的时候呢?

ini 复制代码
public static Message obtain(Message orig) {
    Message m = obtain();
    m.what = orig.what;
    m.arg1 = orig.arg1;
    m.arg2 = orig.arg2;
    m.obj = orig.obj;
    m.replyTo = orig.replyTo;
    m.sendingUid = orig.sendingUid;
    m.workSourceUid = orig.workSourceUid;
    if (orig.data != null) {
        m.data = new Bundle(orig.data);
    }
    m.target = orig.target;
    m.callback = orig.callback;

    return m;
}

可以看出,并没有真的new一个message出来,而是将orig这个message 的信息赋给缓存队列中的一个空内容的mssage里面。这利用到了享元模式的概念。不频繁的创建空间,避免OOM的产生。

相关推荐
Boilermaker19921 小时前
【Java EE】SpringIoC
前端·数据库·spring
中微子1 小时前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁2 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry2 小时前
Fetch 笔记
前端·javascript
拾光拾趣录2 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟2 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan2 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson2 小时前
青苔漫染待客迟
前端·设计模式·架构