安卓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的产生。

相关推荐
子兮曰2 小时前
async/await高级模式:async迭代器、错误边界与并发控制
前端·javascript·github
恋猫de小郭2 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
GIS之路4 小时前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒6 小时前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
Kagol7 小时前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉7 小时前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau7 小时前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生7 小时前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼7 小时前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范
明君879977 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter