一份详细的Handler原理解析

Handler机制(一)——Handler运行流程分析 Handler运行机制 阅读详情

        Handler已经是个老生常谈的问题了,对于Android开发者来说Handler是一个无论面试还是日常开发都无法避开的话题,因为它是Android系统中最重要的事件分发和处理机制。本篇文章就Handler从使用到源码解析再到使用上可能产生相应的问题做一个总结。

        

一、Handler基础

      1、初识Handler

        相信许多小伙伴刚了解Handler的时候是在线程间通信上认识的。比较常用的当一个功能在子线程执行出结果需要在控件上展示的时候,由于在一般情况下子线程不允许更新View,所以需要将子线程的结果提交给主线程,由主线程完成数据的绘制,在这两个线程进行数据传递就需要Handler的帮助。

        再后来根据Handler提供的方法特性,我们可以用来做延迟加载或者开发一个自定义定时器等功能。

      2、基础使用

        Handler的使用比较简单:

        1、定义Handler,接收消息的主体

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
             //此方法用于接受消息,并进一步处理
            super.handleMessage(msg);
            //根据传入的what类型处理不同消息
            switch (msg.what){
//                case msgWhat1:
//                    break;
//                case msgWhat2:
//                    break;
//                    ...
            }
        }
    };

        2、发送消息

        Handler提供了多种发送消息的方法,根据不同需求执行不同方法

//通过复用的方式创建消息体
Message msg = mHandler.obtainMessage();

//发送消息
mHandler.sendMessage(msg)
//发送空消息
mHandler.sendEmptyMessage()
//延迟发送空消息
mHandler.sendEmptyMessageDelayed(what,time)
//延迟发送消息
mHandler.sendMessageDelayed(msg,time)
//发送一个特定时间执行的消息
mHandler.sendMessageAtTime(msg,time)
//发送一个特定时间执行的空消息
mHandler.sendEmptyMessageAtTime(what,time)
//发送消息并插入到队列前,立刻执行
mHandler.sendMessageAtFrontOfQueue(msg)

//除了send系列还有post系列的消息发送,区别在于post发送需要传入
//一个Runnable对象,执行完后不会执行到handleMessage方法中,而是
//直接回调到Runnable的run方法中,post提供的方法与send类似,不
//多做赘述
mHandler.post(new Runnable() {
    @Override
    public void run() {
        //执行代码
    }
});

二、Handler原理

      1、Handler成员

        Handler:消息的接收和发送主体,通过send/post发送消息,在handleMessage或者 Runnable中处理消息

        MessageQueue:消息队列的管理者,虽然名字带着Queue,但是MessageQueue本身并不是队列结构体,真正的结构体是Message,它只是消息队列的管理者,同事内部持有当前线程的Message队列,对消息进行获取、插入、移除等操作

        Message:消息主体,类内部通过Message类型的next变量将Message组成一个单向链表结构

public final class Message implements Parcelable {
    //......
    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    //指向下一条Message,形成链表
    @UnsupportedAppUsage
    /*package*/ Message next;
    //......
}

        Looper:轮询者,内部是一个无限循环,通过不断检测和轮询MessageQueue中是否有消息来分发到Handler的回调方法中。

        

      2、执行流程

        2.1 发送消息

        以sendMessage为例:

//Handler.java

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 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直接取的成员变量
    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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //Message持有当前Handler对象
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //发送消息最终都会调用到MessageQueue的enqueueMessage方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

         发送消息主要做了三件事

        1、检测MessageQueue是否创建

        2、通过msg.target = this 让Message持有当前Handler对象,方便后面调用

        3、最终调用了MessageQueue的enqueueMessage方法

         那MessageQueue.enqueueMessage()又做了什么呢?

//MessageQueue.class

/**
* 当前的消息队列
*/
@UnsupportedAppUsage
Message mMessages;

/**
* 将发送的消息进行排序,插入等处理
*/
boolean enqueueMessage(Message msg, long when) {
    //是否持有了Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //同步锁防止,同一个Hander在不同线程发送消息造成的同步插入问题
    synchronized (this) {
        //正在使用中的抛出异常 与markInUse配合
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //当前消息系统退出了 直接回收Message
        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;
        }
        //改变使用标识位,与isInUse配合
        msg.markInUse();
        //更新Message时间,用于排列
        msg.when = when;
        //获取队列的头指针
        Message p = mMessages;
        boolean needWake;
        //如果当队列没有消息 || 
        //如果当前消息时间为0,为立即执行的消息放在队首 || 
        //当前消息执行时间还小于队列中最近需要执行的消息
        if (p == null || when == 0 || when < p.when) {
            //将当前消息作为队列的头指针
            msg.next = p;
            mMessages = msg;
            //如果阻塞则需要唤起,否则等待loop的轮询执行
            needWake = mBlocked;
        } else {
            // 插入到队列中间。通常我们不需要唤醒事件队列,除非在队列
            //的头部有一个障碍,并且消息是队列中最早的异步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //遍历当前队列插入到队列中间,队列插入算法
            for (;;) {
                prev = p;
                p = p.next;
                //查找第一个时间大于当前消息的Message
                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;
}

        enqueueMessage中主要做了这几件事:

        1、检测线程内是否有消息队列或者当前传入消息的时间是否插入到队列头部,是的话插入队首,否则遍历队列插入对应时间结点中

        2、检测当前轮询是否需要唤醒,在头指针时判断如果当前已经阻塞则需要唤醒队列;如果插入在队里中除了特殊情况之外是不需要唤醒的只需要等待Looper根据时间轮询调用即可

        3、需要唤醒是调用nativeWake(ptr),这是一个native方法底层C实现,用来唤醒通过nativePollOnce阻塞的方法。

        在这里我们需要注意的是插入队列加了一个同步锁synchronized,其实也好理解,发消息用到Handler,然而同一个Handler是可以在不同线程发送消息的,那插入的时候必然会有同步问题,所以需要同步处理。

        消息的发送到插入就这么多,在这个过程中我们可以看出用到MessageQueue,并且它是直接通过成员变量赋值,那么我们还需要了解一下MessageQueue是如何创建的

        2.2 Looper的启动和MessageQueue的创建

        2.2.1 MessageQueue赋值

public class Handler {
    //......
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    //......
    public Handler(@Nullable Callback callback, boolean async) {
        //......
        //Looper通过Looper.myLooper()获取
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
        }
        //MessageQueue通过Looper获取
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}

        可以看出Handler中MessageQueue是通过Looper获取的,而Looper又是通过本身Looper.myLooper创建,所以我们还需要探究Looper的创建。

        2.2.2 Looper创建

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

        Looper.myLooper()方法只是一个获取操作,还需要进一步查看Looper的创建

private Looper(boolean quitAllowed) {
    //创建MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //获取当前线程
    mThread = Thread.currentThread();
}

        Looper构造函数私有对外是没有办法通过new方法创建,需要查看内部提供的方法,并且在构造函数中我们看到了MessageQueue的创建

        通过构造方法我们知道了创建Looper是在Looper的prepare()方法中执行的:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    //如果创建过,抛出异常,保证线程内Looper的唯一
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper存入sThreadLocal池中
    sThreadLocal.set(new Looper(quitAllowed));
}

        prepare()中保证了Looper在线程的唯一性,并且创建Looper存入sThreadLocal中。那ThreadLocal又是什么,如何存入的

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

        ThreadLocal的 java.lang 包下面的一个类,我们现在可以暂时简单把它理解为一个以当前线程为key的Map

public class ThreadLocal<T> {
    //......
    public ThreadLocal() {
    }
    /**
     * 获取当前线程存入的value
     * @return the initial value
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    //......

    /**
     * 获取当前线程作为key,存入数据
     */
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //以线程作为key,存入数据
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    /**
     * 获取当前线程内的ThreadLocal
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
     * 创建字典
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //......
    
    /**
    * 内部类通过K,V方式存入数据,类似Map
    */
    static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //......
    }
}

        ThreadLocal.set()方法是将创建的Looper存入以当前线程为key的字典中,在存入之前通过get()方法先获取当前线程是否有过Looper,创建过抛出异常从而保证了唯一性

        上述代码我们可以了解到:

        一个线程内只能存在一个Looper,一个Looper对应一个MessageQueue,所以一个线程内也只有一个MessageQueue

        2.2.3 Looper的启动

        我们介绍过Looper是一个无限循环,类似传送带一样不断轮询MessageQueue中的消息,那创建完Looper如何启动这个传送带呢?

        这里我们需要用到Looper.loop()方法

//Looper.java

public static void loop() {
    //获取当前线程的Looper
    final Looper me = myLooper();
    if (me == null) {
        //子线程使用Handler没有启动Looper的经典错误
        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;
    //赋值当前Looper中的MessageQueue
    final MessageQueue queue = me.mQueue;

    //......
    
    //开启无限循环
    for (;;) {
        //获取下一个消息
        Message msg = queue.next(); // might block
        //返回null,退出当前循环
        //需要注意 如果当前系统内没有消息是会阻塞线程 并不会返回null
        //返回null,代表了出现了异常或者主动退出
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //......
        try {
            //msg.target是发送消息的Handler对象,所以调用到了
            //发送消息的Handler的dispatchMessage方法
            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);
            }
        }
        //......
        //消息回收,进行复用
        msg.recycleUnchecked();
    }
}

        loop()方法中直接定义了一个for无限循环,通过不断的调用MessageQueue.next(),在获取到Message后再调用target(发送时持有Handler)的dispatchMessage方法进一步处理。

        这里我们需要知道正常情况下MessageQueue.next()要么返回Message,要么没有消息就挂起方法等待新Message插入后唤醒线程再返回,如果返回null则代表整个消息机制异常或者被关闭了,所哟判断MessageQueue.next()==null,就可以直接退出轮询。

        2.3 MessageQueue获取Message

        MessageQueue.next()方法

//MessageQueue.java

@UnsupportedAppUsage
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.
    // ptr是系统分配的一个code,在消息机制退出的时候会赋值0,结束循环
    // 在MessageQueue构造方法中通过nativeInit函数创建
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //空闲时需要执行的任务数
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    //阻塞等待时间 0:立即执行; -1:一直等待; 其他:根据数值挂起等待
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //nextPollTimeoutMillis不为0则说明程序可能要挂起
        if (nextPollTimeoutMillis != 0) {
            //接下来可能会挂起,此方法用于释放挂起对象的引用
            Binder.flushPendingCommands();
        }
        //挂起函数,根据nextPollTimeoutMillis判断时间
        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;
            //找到第一个可执行的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) {//如果存在需要执行的Message
                //判断Message时间与当前时间,是否需要等待
                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.
                    //不需要等待则需要取出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();
                    //返回Message给Looper继续处理
                    return msg;
                }
            } else {
                // No more messages.
                //如果队列中没有可执行的Message,赋值-1为一直等待状态
                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.
            //如果当前没有可执行的Message,程序处于空闲状态,那需要判断
            //有没有需要执行的空闲任务
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                //一般情况下没有其他空闲任务直接进入下一次循环
                //根据nextPollTimeoutMillis=-1,挂起队列
                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.
        //执行完空闲任务后,为了确保是否有新Message,需要重新循环检测
        nextPollTimeoutMillis = 0;
    }
}

        由上总结next()方法的主要工作:

        1、开启无限循环,根据nextPollTimeoutMillis判断是否需要挂起等待。

        2、根据当前时间戳查找需要执行的Message,如果大于当前时间需要计算等待时间,否则返回对应Message给Looper,进一步处理。

        3、如果没有可执行的Message,就判断当前是否有存在空闲任务,如果有则执行空闲任务,否则nextPollTimeoutMillis = -1,在进入下一次循环的时候挂起等待。

        这里有几个问题需要解释一下:

        1、ptr是MessageQueue构造创建通过nativeInit函数创建的code,主要用于当前挂起和唤醒程序的唯一标示

        2、nativePollOnce函数是MessageQueue定义用C实现的底层函数,通过Linux的epoll机制实现挂起,根据nextPollTimeoutMillis的值用于判断当前程序的挂起状态,其中

        nextPollTimeoutMillis = 0;立即执行,无需等待

        nextPollTimeoutMillis = -1;进入挂起状态,等待唤醒

        nextPollTimeoutMillis = 其他数值;挂起等待的时间

        当调用nativePollOnce挂起之后,需要使用nativeWake函数唤醒,也就是我们调用MessageQueue.enqueueMessage()方法插入Message时用到的函数

        当然其中还涉及的Linux底层,感兴趣的小伙伴可以进一步了解

        3、next()获取Message也用到了synchronized锁,这里因为我们在插入的时候可能是其他线程,为了防止边插入边获取带来的同步问题

        4、查找Message比较简单,根据当前时间戳,如果最近的消息时间大于当前时间需要计算等待时间,否则按顺序获取Message返回给Looper

        5、关于IdleHandler空闲任务,我们可以理解为,当前系统如果没有Message执行那代表系统处于空闲时间,我们可以利用这段空闲时间来完成一些并不紧急的任务。等一旦有Message执行,则优先处理Message。

        比如 系统的GC机制,在系统繁忙的时候GC一般是不会进行垃圾回收,当程序空闲时GC就开始进行检测处理系统垃圾。

        执行完空闲任务后,可能会有新的Message进来,所以需要将nextPollTimeoutMillis赋为0重新检测一遍。

        以上就是MessageQueue.next()的主要功能,Looper在获取Message后有提交给了Handler处理,我们继续梳理。

        2.5 Handler处理Message

        根据2.4的介绍Looper获取到Message之后,执行了Message.target.dispatchMessage方法,也就是Handler的dispatchMessage方法:

//Handler.java

public void dispatchMessage(@NonNull Message msg) {
    //优先判断callback是否实现 post方式传入的消息
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //创建Handler时,构造方法提供了传入Callback的重载,如果
        //传入则优先回调Callback
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //定义Handler所实现的回调方法。
        handleMessage(msg);
    }
}

        dispatchMessage比较简单:

        1、先判断了msg.callback,开始我们介绍了Handler发送消息的两种方式(send/post),这里的callback就是通过post方式传入的Runnable

//Handler.java

/**
* post方式
*/
public final boolean post(@NonNull Runnable r) {
   //最终调用了与send相同方法
   return  sendMessageDelayed(getPostMessage(r), 0);
}

/**
* 构建Message
*/
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    //赋值callback
    m.callback = r;
    return m;
}

        2、Handler构造提供了多种重载,除了无参默认的方式,也可以传入Runnable作为回调方法,如果采用默认方式那最终调用到我们所熟悉的handleMessage方法。

        2.6 Message的缓存复用策略

        我们平时发送消息的时候通常使用的是Handler提供Message,而不是直接new 创建。

//使用Hander提供的方法
//Handler从Message的缓存池中找到空闲的Message
Message msg = mHandler.obtainMessage();

        Handler发送消息可能是一个频繁多次的过程,所以频繁的创建Message,用完后如果没有及时回收会造成大量垃圾,造成性能问题,所以我们需要了解Message是如何缓存如何复用。

//Handler.java
Message msg = mHandler.obtainMessage();

@NonNull
public final Message obtainMessage(){
    return Message.obtain(this);
}

//Message.java
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}

//锁
public static final Object sPoolSync = new Object();
//缓存池
private static Message sPool;
public static Message obtain() {
    //同步锁,防止Handler在不同线程提供消息时的同步问题
    synchronized (sPoolSync) {
        //缓存池
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    //缓存池为null 提供新Message
    return new Message();
}

        我们可以看出:

        1、缓存池的本质跟MessageQueue中的队列一样,通过Message.next组成的单向列表结构

        2、获取Message时也加了同步锁是因为sPool是一个static的全局静态变量意味着整个进程只有一个缓存池,同时同一个Handler可能会在不同线程调用obtainMessage()获取Message会有同步问题,所以需要加同步锁。

        3、获取比较简单,当sPool有值时取出队首Message,将sPool指向next下一条Message同时更新size;如果当前sPool为null,即没有可用的缓存直接创建新Message并返回。

        获取就这些,那Message是什么时候存入缓存池中的呢?

        在我们探究Handler流程时,会发现在MessageQueue插入Messgae和Looper用完后回调用Message.recycle()或者Message.recycleUnchecked();

/**
* 不能回收正在使用的消息
*/
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.
 * 回收Message而且这个Message可能被正在使用,用于Message和Looper内部
 */
@UnsupportedAppUsage
void recycleUnchecked() {
    // 清理
    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;
    //与obtain 同一个锁sPoolSync,防止边回收边获取的同步问题
    synchronized (sPoolSync) {
        //判断最大size
        if (sPoolSize < MAX_POOL_SIZE) {
            //将当前回收的Message放到缓存头部节点
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

        回收代码中将使用过的Message内部的各种状态值,参数值初始化。加入缓存池的实质操作是将当前Message作为缓存池的头节点插入队列中,同时防止与obtain获取Message发生同步问题,需要加入与获取Message一样的同步锁

      3、总结

        1、在使用Handler之前需要先调用Looper.prepare()和Looper.loop()

        Looper.prepare()负责创建Looper和MessageQueue,同时将Looper对象存入ThreadLocal中,防止一个线程多次创建

        Looper.loop()负责开启无限轮询,不断调用MessageQueue.next获取消息

        2、Handler通过send或者post方式发送消息,让Message持有当前Handler并最终调用到MessageQueue.enqueueMessage()方法

        3、MessageQueue.enqueueMessage()负责判断将当前Message插入到Message队列中,根据Message.when进行排序,并根据当前执行状态可能需要通过nativeWake()唤醒挂起的队列

        4、Looper.loop()中调用MessageQueue.next()获取Message,在next()中需要判断队列中的Message与当前时间是否需要挂起,如果没有Message或者需要挂起则通过nativePollOnce挂起程序,否则返回Message给Looper

        5、loop()在获取到Message后调用到Handler的dispatchMessage方法,dispatchMessage方法根据发送的方式(send/post)不同,回调到callback或者handleMessage方法中。

三、Handler相关问题     

        整理比较常见的Handler相关问题

        1、子线程如何使用Handler,主线程为什么能直接使用

        根据Handler的原理,使用Handler之前需要初始化MessageQueue和Looper,同时还需要开启轮询,所以子线程中用到Handler之前需要先调用Looper.prepare()和Looper.loop()两个方法

        主线程同样也需要初始化这两个,我们之所以能直接使用是因为在App启动的时候就已经初始化了Looper,在ActivityThread.main()中

public static void main(String[] args) {
    //......
    //创建Looper
    Looper.prepareMainLooper();

    //......
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //......
    //启动轮询
    Looper.loop();

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

        main函数我们都知道是Java程序的入口程序,同样也是Android App的入口程序,在main方法中我们可以看到Looper使用的是prepareMainLooper()方法,方法内部依旧调用了prepare(),只是针对特殊的主线程做了其他处理。

@Deprecated
public static void prepareMainLooper() {
    //创建Looper,并且不可退出
    prepare(false);
    synchronized (Looper.class) {
        //记录主线程Looper
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

        2、使用Handler中存在的内存泄漏几种情况以及解决方法。

        第一种:Activity中匿名声明Handler,在退出是没有清理正在处理的消息

匿名声明的对象会持有外部类引用,也就是Handler会持有Activity引用。而且在发送消息时,Message会持有Handler的引用:msg.target = this //Handler

        Message间接持有了Activity的引用,如果在Activity退出的时候MessageQueue中依旧有未处理的Message的,那Activity也会一直被持有,故而Activity无法被释放回收造成内存泄漏问题。

        解决方法:

        Handler移除所有已知的Message,并且Handler=null来断开Handler与Activity的关联

@Override
  protected void onDestroy() {
    super.onDestroy();
    if(mHandler != null){
        //清理消息,移除外部类引用
        mHandler.removeCallbacksAndMessages(null);
        mHandler = null   
    }    
  }

        第二种:子线程中没有正常退出Handler机制

        子线程使用Handler会通过Looper.prepare()、Looper.loop()开启无限轮询,这就意味着子线程即便代码逻辑已经结束,但Handler机制却一直运行,即便没有Message也会在MessageQueue.next()阻塞从而无法被释放,如果此线程也是通过匿名内部类方式创建那还会持有外部类引用,造成内存泄漏,单单移除Message是没用的。

        解决方法:

        需要手动关闭Looper

MessageQueue.quit()

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

        退出时主要工作:

                1、判断当前任务是否可以退出

                2、修改mQuitting的值

                3、移除所有MEssage

                4、唤醒任务进行判断

        同时配合MessageQueue.next()中对mQuitting的判断返回的Message为null,Looper获取到null值后会return退出轮询从而退出整个消息机制。

        3、既然Looper.loop是无限循环,那主线程为什么没有ANR

        首先我们需要明白ANR是指主线程中被某种原因(CPU过载,内存不足,睡眠...)等原因被阻塞,无法响应和处理当前事件;或者当前正在处理的事件过于耗时没有及时完成,所触发的一种系统保护机制。

        App运行实质是以Handler消息机制为驱动执行的,包括四大组件间的启动运行,事件输入输出等都是在主线程的Looper中运行,包括ANR的检测也是由Handler驱动。所以ANR是Looper执行主线程的Message在没有在指定的时间内响应所触发的机制,与轮询本身没有任何关系。

        也可以简单理解为:ANR是系统阻塞的检测和保护机制,触发的本质也是一种Message发送到主线程的Handler,运行在Looper.loop()中。

        4、Handler是如何进行线程切换的

        线程A创建的Handler,在线程B发送消息,最终在线程A中执行

        通过原理分析我们可以知道发送消息只是插入到队列中,真正获取和处理消息是在Looper中进行的,所以无论在哪个线程发送消息,最终都需要在Looper所在线程进行处理,而Looper在创建的时候就已经和当前线程通过的方式绑定在ThreadLocal,包括MessageQueue。

        所以线程切换的本质是:线程B发送消息插入到线程A的队列中,再由线程A的Looper分发给Hanlder进行处理

        5、Handler、Looper、MessageQueue在同一个线程数量以及对应关系,同时如何保证线程安全问题。

        一个线程内可以创建多个Handler,并且可以在不同线程中使用;

        根据Looper.prepare代码分析,一个线程内只能有一个Looper存放在ThreadLocal中;

        Looper创建时在构造方法内同时创建MessageQueue所以一个线程内只有一个MessageQueue。

        Handler可以在不同线程发送消息插入到MessageQueue,所以在MessageQueue.enqueueMessage中加入了synchronized同步锁防止多线程插入的同步问题,对应的插入在其他线程中那获取时即Looper.next()也需要加入synchronized同步锁,防止边存边取带来的同步问题。这两个锁对象都是MessageQueue,由于一个线程只有一个MessageQueue,所以同步锁是有效的。

        结语:以上就是对Handler原理解析的全部内容,后续也会持续更新和改正其中的不足。

Handler消息机制 文章目录(一) Handler消息机制简介组成消息类型(二) Handler的使用使用方式一使用方式二使用方式三使用方式四使用方式五(三) 消息发送时序图消息机制运行准备说明。发送消息Handler#sendMessage(...)说明发送消息Hnalder#sendMessageDelayed(msg, delayMillis)说明发送消息Handler#post(...)说明IdleHandler说明消息屏障说明其他消息发送说明(四) 使用Handler的注意事项(五) QA(六) 参考 阅读详情

相关推荐

扒一扒面试必问的Handler

前言 Handler作为Android代码编写以及面试时经常遇到的内容,有必要花个时间整理一下,毕竟写过的东西印象会更加深刻。 1.什么是Handler? 1.1 定义 源码里面捞出来的内容,英文不难看懂。主要就是说每个Handler会和每个线程以及线程对应的消息队列相绑定。之后消息就可通过Handler在线程之间传递。 A Handler allows you to send and process {@link Message} and Runnable objects associated

鸿蒙开发知识记录 796

Handler机制详解

Android的消息机制主要是指Handler的运行机制即:消息的发送、入队、出队、分发过程。

Dream It Possible 2490

android中封装handler

一个封装好的handler android中,handler是我们经常打交道的一个class,但在activity或者fragment中,直接new出来,然后在post或者handleMessage的代码一加,再完美的结构也会变得乱糟糟的。有必要封装一个handler,作为日常的结构化使用。下面给出一些代码的片段,来看怎么封装handler。 首先我们给它起一个响亮的名字:UiThreadExecutor,顾名思义就是在ui线程中执行的executor,简单明了。 既然是对handler的封装,handle

weixin_50944054的博客 670

Handler机制

Handler机制

似霰的博客 3402

Handler相关面试题

常见面试题: Handler相关 一.谈一谈你对handler的认识,例如:机制和实现等。 handler机制图解: handler常见使用过程: ①.子线程向主线程发送消息: 在主线程中新建Handler实例,并重写handleMessage()方法用于处理子线程传过来的message; 通过Message.obtain()方法获取到消息msg,然后将需要传递给主线程的信息通过msg的what、obj等字段传递; 通过handler的sendMessage发送msg消息,然后在hand

龚礼鹏的博客 3171

CTF密码学奇技淫巧:当Base64遇到ROT13与Atbash

本文深入解析CTF竞赛中Base64、ROT13与Atbash的复合编码技术,提供Python实现与CyberChef可视化处理方案,帮助选手掌握密码学题目的解题技巧。通过NewStarCTF2025实战案例,对比脚本化与可视化工具的优劣,提升Web安全竞赛中的编码处理能力。

weixin_29001683的博客 509

【MySQL】MySQL 之 handler 的详细使用及说明

起序:在 Web 题遇到的,也算是骚姿势吧,希望帮到有需要的同志。 一、简介 MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。 二、语法 1、官方语法 官方给出的:HANDLER Statement

边扯边淡 1万+

深入解析set_clock_groups:异步时钟约束的最佳实践与陷阱规避

本文深入解析了FPGA/芯片设计中set_clock_groups命令在异步时钟约束中的核心作用与最佳实践。文章对比了同步时钟、异步时钟与不重叠时钟的区别,强调了正确使用set_clock_groups避免时序分析陷阱的重要性,并提供了与set_false_path的优先级差异分析及实战规避方法,帮助设计者确保时序收敛与电路可靠性。

weixin_29232121的博客 376

stm32 各种类型错误:HardFault_Handler、MemManage_Handler、BusFault_Handler、UsageFault_Handler

STM32出现HardFault_Handler故障的原因主要有两个方面: 1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。 2、堆栈溢出。增加堆栈的大小。 MemManage_Handler:访问了内存管理单元(MPU)定义的不合法的内存区域,比如向只读区域写入数据。 BusFault_Handler:在fetch指令、数据读写、fetch中断向量或中断时存储恢复寄存器栈情况下,检测到内存访问错误则产生 BusFault。 UsageFault...

灵哎惹,凌沃敏的博客 1万+

LAP-D协议概述

Q.921协议由称为LAPD(链路访问协议-D通道),与HDLC的关系密切,它是在ISDN D通道上使用的数据链路协议。 注:http://www.freesoft.org/CIE/Topics/125.htm LAP-D的帧格式 标志、位填充技术和FCS校验计算与HDLC一致。 16位的地址包括C/R字段、SAPI(服务访问点标识)和TEI(端点标识)。TEI用于区分使用相同ISDN...

系统运维 1914

从零开始利用MATLAB进行FPGA设计(一):建立脉冲检测模型的Simulink模型2

上一期分析了MATLAB的黄金算法原理,这一期进行Simulink模块分析。

WZT725的博客 2087

Handler完全解读——Handler的使用

Handler完全解读——Handler的使用 Handler是什么 Handler是Android给我们提供用于更新UI的一套机制,也是一套消息处理机制。我们用它可以发送消息,也可以用它处理消息。在Android开发中有着非常重要的地位。 为什么要使用Handler 当一个应用程序运行时,它会创建一个进程。这个进程就是我们的主线程(UI线程&amp;Activity Thread) 。在主线程中...

N0tExpecterr0r的博客 8216

2026保姆级:1小时搞定C# OPC UA客户端开发——PLC与上位机全量数据交互零基础教程

本文介绍了使用C#开发OPC UA客户端的核心流程和关键代码实现。文章首先指出工业上位机开发中OPC UA的重要性,并针对新手常见问题提出了1小时掌握核心功能的解决方案。重点内容包括:选择官方.NET Standard库的优势、5分钟搭建开发环境的步骤、OPC UA客户端架构与通信流程的图解说明,以及完整的客户端基础类代码实现,涵盖了连接管理、证书配置、安全认证等关键功能。通过本文提供的代码模板,开发者可快速实现与PLC等工业设备的稳定数据交互。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 803

再论Handler—Handler事件分发、Handler线程切换原理解析

再论Handler—Handler原理解析 Handler,无论你菜鸟还是像我一样的老菜鸟,都无法避免这个亘古不灭的话题,只有了解学会Handler的原理,你才能变成一个出的厅堂下得厨房的优秀的老菜鸟。其实同类优秀的帖子已经很多了,但是为了强化记忆我的理解,还是想再次记一下,我心中的Handler。 要介绍Handler,就必须要介绍与它息息相关甚至“同为一体”的其它几个类:Looper、MessageQueue、Message。所以本篇文章就分为三个部分: Handler、Looper、MessageQ

blankmargin的博客 2636

【大模型】SpringBoot整合LangChain4j实现RAG检索实战详解

SpringBoot整合LangChain4j实现RAG检索实战详解

congge 1万+

Doris磁盘空间管理(生命周期管理、清理磁盘空间方法)

在进行数据仓库开发时,比较头疼的是历史数据不删除,磁盘空间不断增长,如果磁盘空间不足时,spark调度任务写入时就会报错。

大模型大数据攻城狮的专栏 1719

全局Handler的封装

相信大家写Android项目的时候,使用Handler发送消息是非常常见的,可以说在项目中有非常多的地方使用到Handler,于是这时候考虑怎么去封装一个Handler,使用的时候也比较方便,又可以避免重复去创建Handler对象,我们会想到全局就使用一个Handler,那么怎么全局去封装一个Handler的,我是这么来设计的:     首先定义一个类GlobalHandler,继承...

Crazyencode的博客 4665

PGM-Explainer实验总结

PGM-Explainer建立了一个概率图模型,为GNN提供实例级解释。 论文标题:PGM-Explainer: Probabilistic Graphical Model Explanations for Graph Neural Networks 论文地址:2010.05788.pdf (arxiv.org) PGM-Explainer实验 实验目的 研究PGM-Explainer的有效性 实验方案 合成数据集的节点分类 与其他解释器做对比实验 使用六个合成数据集。 每个输入图都是一个

笔记|思路|总结 1万+

asio中的handler

申请和释放!defineddefined对于其它重载的最后是调用命名空闲的allocate和deallocate,最后就是调用命令空间和!defined!definednewdelete以linux平台为例fill:#333;stroke:1;fill:none;important;important;important;important;important;important;important;important;important;

多看多听多总结 1039

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Handler的实现原理

处理过程:从handler中获取一个消息对象,把数据封装到消息对象中,通过handler的send…方法把消息push到MessageQueue队列中。Looper对象会轮询MessageQueue队列,把消息对象取出。通过dispatchMessage分发给Handler,再回到调用Handler实现的handleMessage方法处理消息。流程图:Handler的实现中适及以下对象:1、:负责消息的发送和处理2、:消息对象3、:消息队列(用于存放消息对象的数据结构)4、

Hunter2916的博客 831

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

MySQL关于HANDLER命令的说明

文章目录一、 语法二、为什么要用HANDLER而不是“SELECT”语句 一、 语法 官方给出的格式: HANDLER tbl_name OPEN [ [AS] alias] HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_cond...

miaoyibo12的博客 759

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Ansible的handler

Ansible的handler

duke_ding2的博客 1033

mysql handler 详解_Mysql HANDLER语句

HANDLER语法HANDLER tbl_name OPEN [ [AS] alias]HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)[ WHERE where_condition ] [LIMIT ... ]HANDLER tbl_name READ index_na...

weixin_30641041的博客 695

全局handler

全局Handler封装,优雅的书写handler的UI刷新界面 在Android开发中我们会经常使用handler进行ui界面的刷新,这主要是源于Android的线程机制和消息机制。Android不允许开发者在主线程中刷新ui界面,所以我们会经常用到消息机制,但是频繁的创建handler有可能导致内存泄漏,而且容易造成代码冗余,影响代码质量,所以我们要设置一个全局的handler,通过接口实现来实现ui刷新。 请看代码: import android.os.Handler; import andro

Android_data的博客 1554

handler——handler的使用例子

转自http://blog.youkuaiyun.com/fdaopeng/article/details/7863840 Android UI操作并不是线程安全的,所以无法在子线程中更新UI。但Andriod提供了几种方法,可以在子线程中通知UI线程更新界面: Activity.runOnUiThread( Runnable ) View.post( Runnable ) V

chuntiandejiaobu10的专栏 393

Android Handler机制

《Android Handler机制》 《Android Handler分析(一) Handler和Message详解》 《Android Handler分析 (二) MessageQueue详解》 《Android Handler分析 (三) Looper详解和Handler其他知识》 《Android 中的 HandlerThread 类详解》 作为一个Android程序员,我想大家都知道在做一些比较耗时的操作的时候都不会放在主线程,比如网络请求、数据库操作等(尤其是网络请求,在Android4.0.

ITRenj的博客 361
上一篇: Java 单例模式的二三事
下一篇: Android Launcher和SystemUI的启动
Code_dh
博客等级 码龄8年 58粉丝 4原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值