Android基础相关

Handler的几个问题

2021-01-04  本文已影响0人  来lol里

首先就是通过Looer启动loop这个方法,让MessageQueue初始化,然后当你想给哪个线程发消息,就使用那个线程的Handler对象调用sendMessage(message),这时候消息会被发送到消息队列里,然后MessageQueue里的next()方法会计算时间,当这个message时间到了就会调用handler.dispatchMessage这个方法发送到当前持有Looper的这个线程,也就是我们的目标线程。
其实总结起来,handler跨线程通信的核心就是,当你想给哪个线程发消息,就使用那个线程的Handler对象调用sendMessage(message)。再来看一下简单的时序图,不涉及native层面的


未命名文件.png
    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));
    }

可以看出抛出的异常Only one Looper may be created per thread 就是每个thread只能有一个Looper,那又是如何保证的呢?我们进一步去看这个 sThreadLocal的set方法

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

再看一下这个values的put方法吧

  void put(ThreadLocal<?> key, Object value) {
            cleanUp();


            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];
                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }
                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

这个values维护的是一个类似hashmap的键值对集合,实际上Java的实现就是如此,(这里的实现方式和arraymap类型,google好像在Android上主推的就是这种SparseArray、ArrayMap用来代替HashMap,手机上很少有很大,数据一千以内的性能会比hashmap强)而Android SDK里用的是一个Object数组,下标偶数的作为key,奇数下标的作为value,从而实现key 和value的一一对应。实际上是通过Threadlocal对象为键, Looper对象为值的方式,保证了线程和Looper对象唯一对应的关系。

 private Button btn;
    Handler handler =new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity.this.btn;
            btn;
        }
    };

我们在handleMessage的回调里能很轻松的持有这个当前Activity的引用,其实这就是问题的所在,内部类持有外部类的引用,当外部类销毁的时候内部类还存在,比如handler执行了一个delay的方法的时候。我们继续追下去,无论handler的任何send或者post方法都会最终调用这个方法

  public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

注意这时候有个 msg.target = this Message 会持有一个对 Handler 的引用,当这个 Handler 是非静态内部类的时候,又会持有一个对外部类的引用(比如Activity)。所以是有一个持有的关系MessageQueue>message>handler>this(如Activity),所以如果是延时消息messge一直存在,导致如果Activity销毁的时候就泄漏了。
解决方式就是静态类+弱引用的方式

  public static void main(String[] args) {
        Looper.prepareMainLooper();
        xxxxxx
        Looper.loop();

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

如果我们要在子线程中使用的话,就必须要先prepare然后再loop。
使用完了如果需要销毁的话可以使用MessageQueue中的这个方法,可以看到主线程中是不可以退出的,只有我们在子线程中才可以使用。

    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);
        }
    }
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;
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) { // 这里会无限循环
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        msg.recycleUnchecked();
    }
}

当MessageQueue中无消息时,queue.next()会阻塞在nativePollOnce(),这里涉及到Linux pipe/epoll机制,nativePollOnce()被阻塞时,如果进一步的往下追到native方法里,是调用的epoll.wait()的方法,这时候主线程会释放CPU资源,进入休眠状态. 直到下个消息到达或者有事务发生,会通过pipe管道写入数据来唤醒主线程工作。

这里主线程进入休眠状态和死循环是有区别的:
死循环是指主线程死锁在这里,一直执行某一块代码,无法再响应其他事件。
休眠状态是指在内核状态里,主线程被挂起,线程状态转移到休眠状态。(核心还是linux的epoll.wait()方法)

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();
    }
上一篇下一篇

猜你喜欢

热点阅读