Android 之 Handler

news2024/10/7 4:26:43

Android 的消息机制是基于Handler实现的。Handler 采用的是一种生产者-消费者模型,Handler 就是生产者,通过它可以生产需要执行的任务。而 Looper 则是消费者,不断从 MessageQueue 中取出Message 对这些消息进行消费。
MesageQueue是在底层调用native方法去创建的,本质是一个单链表;
一个线程只能有一个Looper,对应一个MessageQueue。

RxBus、EventBus、Rxjava 中跨线程(子线程到主线程)通信都是通过Handler进行的

消息机制的原理

由Message类 、Handler类、MessageQueue类 、Looper类 四个类相互之间协同完成的。
Handler 通过静态方法obtain() 获得 message,然后Handler把message 发送给(send)MessageQueue(消息队列:有插队机制),根据延时的大小,进行排序或插入,looper循环器(自我唤醒机制,当没有消息时或延时没到时 处于阻塞状态,该阻塞状态不同于多线程中的)去MessageQueue中取,Handler 调用,在操作,操作完成之后,通过clean() 方法(洗的是:what 的标识,数据…),再放进消息池。

在这里插入图片描述
生产者消费者模式
在这里插入图片描述

基本用法

        Handler handler = new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                // to do something
            }
        };
        handler.sendEmptyMessage(0);
        //handler.post(runnable);

发送消息

消息的发送主要有post 及 sendMessage,它们都有对应的 delay 方法。

        handler.sendEmptyMessage(0);
        handler.post(runnable);
   public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
    }
    public final boolean post(@NonNull Runnable r) {
        return sendMessageDelayed(getPostMessage(r), 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

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

在 sendMessageAtTime 中,它首先通过 mQueue 拿到了对应的MessageQueue 对象,然后调用了 enqueueMessage 方法将 Message 发送至MessageQueue 中

    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 的 target 设置为了当前的 Handler,同时要注意看到,这里在 enqueueMessage 之前先判断了一下 mAsynchronous是否为 true,若为 true 则将该 Message 的 Asynchronous 置为 true

那这个 mAsynchronous 是什么时候被赋值的呢?点进去看可以发现它的赋值是在 Handler 的构造函数中进行的。也就是说创建的 Handler 时若将 async置为 true 则该 Handler 发出的 Message 都会被设为 Async,也就是异步消息

  public Handler(Callback callback, boolean async)
  public Handler(Looper looper, Callback callback, boolean async)

的风格

不论是 post 还是 sendMessage,都会调用到 sendMessag。
sendEmptyMessage(int)
 -> sendEmptyMessageDelayed(int,int)
	 -> sendMessageAtTime(Message,long)
		  -> enqueueMessage(MessageQueue,Message,long)
			-> queue.enqueueMessage(Message, long);

消息的处理

消息的处理是通过 Handler 的 dispatchMessage 实现的:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

它优先调用了 Message 的 callback,若没有 callback 则会调用 Handler 中Callback 的 handleMessage 方法,若其仍没定义则最终会调用到 Handler 自身所实现的 handleMessage 方法。

Message

消息的创建 通过obtain获取,还可通过new Message()方法

private static final int MAX_POOL_SIZE = 50;


    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

    /**
     * Same as {@link #obtain()}, but copies the values of an existing
     * message (including its target) into the new one.
     * @param orig Original message to copy.
     * @return A Message object from the global pool.
     */
    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;
    }

    /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

    /**
     * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
     * the Message that is returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @param callback Runnable that will execute when the message is handled.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values for both <em>target</em> and
     * <em>what</em> members on the Message.
     * @param h  Value to assign to the <em>target</em> member.
     * @param what  Value to assign to the <em>what</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what) {
        Message m = obtain();
        m.target = h;
        m.what = what;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
     * members.
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param obj  The <em>object</em> method to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
     * <em>arg1</em>, and <em>arg2</em> members.
     *
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @return  A Message object from the global pool.
     */
    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;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
     * <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
     *
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @param obj  The <em>obj</em> value to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what,
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

消息的清理 主要是recycleUnchecked的方法的处理。


    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    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++;
            }
        }
    }

MessageQueue

private native static long nativeInit(); //初始化消息队列
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/   //消息的循环与阻塞
private native static void nativeWake(long ptr);//消息的分送与唤醒 

它们分别对应了 Native 消息队列中的 初始化消息队列、 消息的循环与阻塞 以及 消息的分送与唤醒 这三大环节

MessageQueue中存储的Message是没有数量上限的。

消息入队列(排序)enqueueMessage ,通过消息的等待时间进行入队列。

首先在Handler中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);
    }

到MessageQueue的enqueueMessage()

 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;
           // 1.取出当前的消息,如果消息等待时间为0或者小于当前消息的等待时间,直接插到当前消息的前面       
           // 2.如果不是上面的情况:循环遍历已经存在的消息,插入到等待时间 大于前一个并且 小于后一个的位置,排队。
            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;
    }

Looper

消息出队列

  /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;

        // 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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        me.mSlowDeliveryDetected = false;

        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

调用loopOnce方法
mQueue.next(); //调用的是MessageQueue中的 next() 方法

 /**
     * Poll and deliver single message, return true if the outer loop should continue.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what);
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;

        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (logSlowDelivery) {
            if (me.mSlowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    me.mSlowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    me.mSlowDeliveryDetected = true;
                }
            }
        }
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();

        return true;
    }

MessageQueue的next()

 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();
            }
			
			//与 Handler 的阻塞唤醒机制有关
            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;
                //判断当前的消息是否是target 为 null 的消息,若 target 为 null,则它会不断地向下取 Message,直到遇到一个异步的消息
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                //判断当前消息是否到了应该发送的时间,若到了应该发送的时间,就会将该消息取出并返回,否则仅仅是将 nextPollTimeoutMillis 置为了剩余的时间(这里为了防止 int 越界做了防越界处理)
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //第一次循环的前提下,若 MessageQueue 为空或者消息未来才会执行,则会尝试去执行一些 idleHandler,并在执行后将pendingIdleHandlerCount 置为 0 避免下次再次执行。
                //若这一次拿到的消息不是现在该执行的,那么会再次调用到 nativePollOnce,并且此次的 nextPollTimeoutMillis 不再为 0 了,这与阻塞唤醒机制有关。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

MessageQueue next() 方法,中的nativePollOnce本地方法,此处发生了阻塞,(nativePollOnce消息的循环与阻塞)

与阻塞对应的是唤醒机制在入队的时候enqueueMessage方法。

同步屏障机制

Handler 中存在着一种叫做同步屏障的机制,它可以实现异步消息优先执行的功能。

作用

当MessageQueue的next中遇到了一个同步屏障,则它会不断地忽略后面的同步消息直到遇到一个异步的消息,目的是为了使得当队列中遇到同步屏障时,则会使得异步的消息优先执行,这样就可以使得一些消息优先执行。

加入同步屏障

Handler 中还存在一种特殊的消息,它的 target 为 null,并不会被消费,仅是作为一个标识处于 MessageQueue 中。它就是 SyncBarrier (同步屏障),这种特殊的消息。我们可以通过 MessageQueue的postSyncBarrier 方法将其加入消息队列

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

移除同步屏障

   /**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @TestApi
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //找到 target 为 null 且 token 相同的
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

一般执行完了异步消息后就会通过该方法将同步屏障移除。

问题

  1. handler 造成内存泄漏
    Handler 允许发送延时消息,在延时期间用户关闭了 Activity,该 Activity 会泄露。是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
    方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
    举个例子:
    都知道 匿名内部类会持有外部类的引用,也就是说这里的 Handler 会持有其外部类 XXActivity 的引用。而我们可以回忆一下 sendMessage 的过程中,它会将 Message 的 target 设置为 Handler,也就是说明这个 Message 持有了 mHandler 的引用。那么我们假设通过 mHandler 发送了一个 2 分钟后的延时消息,在两分钟还没到的时候,我们关闭了界面。按道理来说此时 Activity可以被 GC 回收,但由于此时 Message 还处于 MessageQueue 中,MessageQueue 这个对象持有了 Message 的引用,Message 又持有了我们的Handler 的引用,同时由于 Handler 又持有了其外部类 XXActivity 的引用。这就导致此时 XXActivity 仍然是可达的,因此导致 XXActivity 无法被 GC回收,这就造成了内存泄漏
    代码如下:
private static class MyHandler extends Handler {

    private WeakReference<Activity> ref;

    public MyHandler(Activity activity) {
        this.ref = new WeakReference(activity);
    }

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

当外部类结束生命周期时,清空 Handler 内消息队列

  @Override
    protected void onDestroy() {
        if (myHandler!= null) {
            myHandler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }
  1. 为什么我们在主线程中直接使用 Handler,而不需要创建 Looper ?
 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        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(); 代码如下:

 /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. See also: {@link #prepare()}
     *
     * @deprecated The main looper for your application is created by the Android environment,
     *   so you should never need to call this function yourself.
     */
    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

然后调用 prepare方法,至此创建looper

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }
  1. 子线程里弹 Toast 的正确姿势。在子线程里直接去弹 Toast 的时候,会 crash :
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

本质上是因为 Toast 的实现依赖于 Handler。
正确示例代码如下:

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

  1. 在非主线程中直接new Handler() 会报如下的错误:
	E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception
	E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called 			    Looper.prepare()

原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。

class MyThread extends Thread {
      public Handler mHandler;
     
      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // to do something
              }
          };
         
          Looper.loop();
      }
}
  1. Looper的创建?一个线程只能有一个Looper?

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

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

prepare的实质:

  • 创建Looper 对象
  • 将创建好的Looper对象存储到ThreadLocal对象里
    注:ThreadLocal对象在创建Looper的时候创建的

ThreadLocal 存储Looper 的处理方式,是保证ThreadLocal里没有其他的Looper,只允许有一份Looper.
这就是为什么同一个Thread线程里只能有一个Looper对象的原因

  1. IdleHandler 有什么用?
    IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
    当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
  2. MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?
    不是必须;IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;
    8.当 mIdleHanders 一直不为空时,为什么不会进入死循环?
    只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
  3. 是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?
    不建议;IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;
    10.IdleHandler 的 queueIdle() 运行在那个线程?
    陷阱问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
    子线程一样可以构造 Looper,并添加 IdleHandler;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/692340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

maven-mvnd(新版maven)

引言&#xff1a; Maven和Gradle几乎包含了所有Java项目的构建。 而目前绝大部分的Java项目都是使用Maven的方式&#xff0c;Maven对比Gradle的劣势很明显&#xff0c;就是太慢了&#xff01; 一、maven-mvnd是什么&#xff1f; maven-mvnd是Apache Maven团队开发的maven的…

python学习笔记-1

文章目录 cmd中写py基本语法字面量字符串 注释变量数据类型数据类型转换标识符 cmd中写py 如果我没有素质&#xff0c;就说我是日本人 基本语法 字面量 字面量&#xff1a;在代码中&#xff0c;被写下来的&#xff0c;固定的值&#xff0c;称之为字面量。有六种值&#xff0…

下载iOS开发教程:下载和安装指南

目录 引言 一. 下载安装appuploader windows启动 部分功能不可用处理 驱动安装 二. 登录appuploader 常规使用登录方法 验证码说明 使用上传专用密码登录 未支付688给apple账号登录 [ 五、代码实现](# 五、代码实现) 六、总结 引言 这里汇总了开发相关过程中的热门…

【vue element-ui 】el-table中使用checkbox视图更新滞后

本来想通过列表中每个对象的某个属性绑定到checkbox的状态&#xff0c;但是发现有个问题&#xff1a;就是点击复选框后&#xff0c;数据确实改变了&#xff0c;但是视图没有改变&#xff0c;当点击其他row的时候&#xff0c;才会更新之前的数图。如下图&#xff0c;第1次勾选第…

【JavaScript】严格模式use strict

use strict 它不是一条语句&#xff0c;但是是一个字面量表达式声明&#xff0c;在 JavaScript 旧版本中会被忽略。 为什么使用严格模式use strict: 消除Javascript语法的一些不合理、不严谨之处&#xff0c;减少一些怪异行为; 消除代码运行的一些不安全之处&#xff0c;保证代…

Jmeter调试取样器(Debug Sampler)

大家在调试 JMeter 脚本时有没有如下几种需求&#xff1a; 我想知道参数化的变量取值是否正确&#xff01;我想知道正则表达式提取器&#xff08;或json提取器&#xff09;提取的值是否正确&#xff01;我想知道 JMeter 属性&#xff01;调试时服务器返回些什么内容&#xff0…

复杂数组的处理方法之多维数组扁平化

1.需求: 将数组[1&#xff0c;2&#xff0c;[3&#xff0c;4&#xff0c;[5&#xff0c;6]]&#xff0c;7&#xff0c;[8&#xff0c;[9&#xff0c;10]]] 转换为 [1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;8&#xff0c…

五、HAL_Timer的定时功能

1、开发环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 2、定时器简介 (1)定时器可以通过输入的时钟源进行计数&#xff0c;从而达到定时的功能。 3、实验目的&原理图 3.1、实验目的 (1)通过定时器设置定时&#xff0c;实现LED灯以500…

13 - 信号可靠性剖析

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;Linux系统编程训练营 - 目录 文章目录 1. 信号的可靠性1.1 问题1.2 信号查看&#xff08;kill -l&#xff09;1.3 信号的分类1.3.1 不可靠信号&#xff08;传统信号&#xff09;…

模拟仿真 OSC振荡器

用五个反相器做一个环形振荡器 跑瞬态仿真 发现并未振荡 手动添加起振 成功振荡 将上面的图像send to calculate&#xff0c;调用频率函数freq 可以看到振荡频率为2.5GHZ左右 如果想要降低振荡频率&#xff0c;可以在每个反相器后加寄生电容或者增大反相器尺寸&#xff0c;这…

ProGuard详解 - Java代码混淆

(29条消息) ProGuard详解 - Java代码混淆_黎陌MLing的博客-CSDN博客

【算法题】动态规划基础阶段之 爬楼梯 和 杨辉三角

动态规划基础阶段之爬楼梯和杨辉三角 前言二、爬楼梯2.1、思路2.2、代码实现 三、杨辉三角3.1、思路3.2、代码实现 四、杨辉三角2&#xff08;进阶&#xff09;总结 前言 动态规划&#xff08;Dynamic Programming&#xff0c;简称 DP&#xff09;是一种解决多阶段决策过程最优…

脱机下载程序

一&#xff0c;脱机下载工具 Mini-Pro V2 版 二&#xff0c;配置stm32CubeIDE 生成hex文件 三&#xff0c;脱机下载步骤 1&#xff0c;连接设备&#xff0c;选择芯片 2&#xff0c; 添加固件。 3&#xff0c;选项字节。 4&#xff0c;生成镜像文件&#xff0c;这个文件包含了…

Mysql索引失效情况及避免方式【案例分析】

索引失效情况及避免方式 建表数据sql CREATE TABLE staffs( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(24)NOT NULL DEFAULT COMMENT姓名, age INT NOT NULL DEFAULT 0 COMMENT年龄, pos VARCHAR(20) NOT NULL DEFAULT COMMENT职位, add_time TIMESTAMP NOT NULL DEF…

json数据、日期数据的参数传递及响应

文章目录 1.json数据传参1.1 postman如何发送json数据1.2 发送json数据&#xff0c;控制器如何接收 2. 日期类型参数传递2.1 日期类型参数如何指定格式 3.响应数据3.1 ResponseBody注解的使用 1.json数据传参 首先在maven中添加json坐标 1.1 postman如何发送json数据 1.2 发…

00后测试用例写的实在是.......

实在是太强了&#xff0c;00后测试用例写的比我还好&#xff0c;简直是无地自容… 经常看到无论是刚入职场的新人&#xff0c;还是工作了一段时间的老人&#xff0c;都会对编写测试用例感到困扰&#xff1f;例如&#xff1a; 如何编写测试用例&#xff1f; 作为一个测试新人…

以太网频谱

Speed频谱100BASE-TX1GBASE-T2.5GBASE-T5GBASE-T10GBASE-T

网络程序——定时器

网络程序还有一种需要处理的常用事件——定时事件。服务器程序通常管理着众多定时事件&#xff0c;因此如何有效地组织这些定时事件&#xff0c;使之能在预期的时间点被触发且不影响服务器的主要逻辑&#xff0c;对于服务器的性能有着至关重要的影响。为此&#xff0c;我们要将…

递归 到底应该怎么理解?怎么写递归的代码

今天和大家一起来讨论一下递归&#xff1a; 我们尽可能使按照解题的思路来讨论递归&#xff0c;对于这个在计算机内部具体是怎样实现的&#xff0c;我们不做深入讨论&#xff0c;这里仅仅是简单的讨论一下&#xff1a; 求1 ~ n序列的和&#xff1a;1 2 3 ... n&#xff1…