Android笔记:(5)Handler的使用和原理解析

Handler是什么

Handler是Android一种重要的消息机制(由Handler、Looper、MessageQueue、Message等构成),主要是为了完成以下几个功能:

  • 我们都知道Android UI线程中不可以做耗时操作,且更新UI的操作只允许在UI线程中进行,所有的耗时操作都需要在子线程中进行,那么当子线程完成耗时操作后怎么切换到UI线程更新UI呢,这时候Handler就可以派上用场了,可以发送Message或者Runnable(这个在Handler内部也会被包装成Message),到指定线程(Handler所在的线程)执行对应的逻辑;
  • 通过内部的消息队列机制,可以将消息入队,在指定的时间点执行(后面会分析原理)。

Handler的使用

Handler的构造函数

  • 无参构造函数,会获得当前线程私有的Looper对象,ThreadLocal保证线程私有,如果为null,那么会报异常;
  • Handler(Looper looper),传入looper对象
  • Handler(Callback),指定回调对象
  • Handler(boolean async),消息是否为异步消息
    当然还有一些组合形式的构造函数,这里就不一一例举出来了。

Handler的初始化和消息处理方法

如果是在主线程中,不用开启Looper,因为在ActivityThread中就执行了Looper.prepareMainLooper();和Looper.loop();开启了主线程消息队列的无限循环。但是在非UI线程中,需要调用Looper.prepare()和Looper.loop(),后文再详细分析Looper的原理。
下面是Handler的简单使用:

private Handler mHandler = new Handler(){
	@Override
    public void handleMessage(Message msg) {
         // 执行相应的逻辑,Message对象就是传递过来的消息,后文会对其做介绍
     }
}

但是如果仅仅只是这样使用,可能会造成内存泄漏。原因在于,非静态内部类会持有外部类的引用,如果是在Activity中使用,而Handler的生命周期和外部类Activity的生命周期不一致,Activity退出之后,仍然被Handler持有引用,无法回收Activity,导致内存泄漏。
如何解决这种情况呢?可以在外部类退出的时候,主动将Handler中没有完成的回调或者消息处理直接移除,以Activity为例,可以在OnDestroy方法中调用:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
    }
    mHandler = null;
}

另外还有一种用静态内部类和弱引用结合的方式:(关于java中的四种引用方式,有时间再总结一下)

private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
        reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
        ForthActivity activity = (ForthActivity) reference.get();
        if (activity != null) {
            // 执行相应的逻辑
        }
    }
}

Handler之消息发送

有关Handler发送消息到指定线程的方法,有如下几种,分为post和send两大类:
post:参数为Runnable类型,但是内部处理也封装成了Message对象

private static Message getPostMessage(Runnable r) {
	Message m = Message.obtain();
    m.callback = r;
    return m;
}

post相关方法
send:参数为Message类型
在这里插入图片描述

Message

Message对象是Android为了Handler消息机制封装的一个消息类。为了使用方便,它提供了如下的属性:

public int what;//用于指代消息类型,而且也不需要担心与其他Handler的消息类型冲突,每个Handler的消息类型是独立的,这是由于target属性中绑定了Handler
// 提供了两个简单的int类型的属性,可以传递简单的int类型消息
public int arg1;
public int arg2;

// 同样单个Object也可以通过这个属性传递
public Object obj;
// 跨进程通信的时候使用,内部是Binder机制
public Messenger replyTo;
// 消息执行的时间,MessageQueue就是根据when属性的先后进行排序的
long when;
Bundle data;
// target往往存储着发送消息的Handler,为了能在Handler所在的线程中处理消息的内容,达到线程切换的目的
Handler target;
// post系列的方法,会将Runnable赋值给它
Runnable callback;

另外值得注意的是,Message是一个链表结构,消息队列也是基于这个链表结构来进行存储消息的。
还有Message内部维护了一个全局消息池,可以循环利用已有的消息实例,不需要重复创建。使用Message.obtain()获得全局消息池链表头部的消息对象,

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

在使用完Message之后,可以调用recycle()进行回收Message对象,也就是将该对象插到全局消息池的头部,并且置为初始状态。但是其实这个动作,在MessageQueue对象中已经帮我们做了,不需要我们手动进行回收~

Looper

一个线程中最多只有一个Looper对象,而且是线程私有的,通过ThreadLocal保证线程私有(ThreadLocal原理深入解析),使用Handler,当前线程必须要有Looper对象。Looper就是提供了一种消息循环处理的机制。
Looper.prepare()开始,就创建了一个Looper对象在当前线程中,而且创建了一个MessageQueue对象与之绑定,在一个线程中不可以多次调用Looper.prepare(),否则会报异常。
下一步,就是开启Looper.loop(),在一个死循环中分发消息队列中的消息到对应的Handler中进行处理,源码如下:

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;
		// 省略一些。。。
       
       // 开启死循环
        for (;;) {
        	// 关键,后文会分析为什么next会阻塞
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
			// 省略一些日志处理...
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
            	// 将消息分发到对应的Handler进行处理,那为什么是调用dispatchMessage,而不是handleMessage呢?这个下文再说
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            //...
			// 将消息回收到消息池中,以便下次利用
            msg.recycleUnchecked();
        }
    }

以上我觉得重要的代码,也就三行:

  • Message msg = queue.next();获取下一条消息,没有就阻塞
  • msg.target.dispatchMessage(msg);分发消息
  • msg.recycleUnchecked();回收消息
    最后回到上面提出的问题,为什么是调用dispatchMessage,而不是handleMessage呢?
    这个我们看一下源码,就知道了,主要是由于有三种回调处理消息的方式:
public void dispatchMessage(Message msg) {
	// 优先级最高的是通过post进来的Runnable对象
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
    	// Handler构造函数可以指定Callback对象,。。。
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 最后才是复写的handleMessage处理逻辑
        handleMessage(msg);
    }
}

MessageQueue

终于总结到了消息队列部分,前文提到通过Looper.loop()循环处理消息队列中的消息,那么在MessageQueue.next()方法中到底做了些什么事情呢?
首先需要说明的是,消息队列中消息的类型有以下三种:

  • 同步消息:这种消息就是老老实实根据when属性在消息队列中排队,等待处理;
  • 异步消息:这种消息也是需要根据when属性在消息队列中排队,但是不会被SyncBarrier所阻挡,只要到了执行的时间就会执行;
  • SyncBarrier(同步屏障):根据when属性插入到消息队列中,如果这个不移除,后面的同步消息都无法执行。postSyncBarrier(long when)添加同步屏障,removeSyncBarrier(int token)移除同步屏障,token是postSyncBarrier(long when)返回的值。target属性为null,指代该消息为同步屏障

接着分析MessageQueue.next(),源码如下:

Message next() {
        // 这个字段是记录的native层,MessageQueue对象的指针,由nativeInit进行初始化
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
		// IdleHandler的个数
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 这个指代线程阻塞的时间,0:不阻塞,-1:一直阻塞,正整数:阻塞的时间(毫秒)
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			// 根据时长进行阻塞,native层的方法,涉及到Linux IO的知识
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 如果第一个消息是同步屏障,那么找到消息队列中的异步消息
                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) {
                    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;
                }

                // 如果执行到这里还没有返回,那么是消息队列中没有消息或者第一个消息还没有执行(可能是一个同步屏障),那么执行IdleHandler中的任务
                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;

            //这个是为了让线程不阻塞,执行IdleHandler中的任务
            nextPollTimeoutMillis = 0;
        }
    }
  • 根据nextPollTimeoutMillis的值决定线程是不是需要阻塞,这种情况一般会发生在消息队列为空或者消息队列中的消息还没有到执行的时机,并且也没有IdleHandler可以执行,那么会通过调用nativePollOnce(ptr, nextPollTimeoutMillis);阻塞线程。那么为什么能够阻塞和唤醒线程呢?这个涉及到Linux IO底层原理,Linux IO模式及 select、poll、epoll详解
  • 如果线程没有阻塞,就会接着往下执行,判断第一个消息是不是同步屏障,如果是就找出后序的第一个异步消息,反正就是找到一个消息并且返回,如果找不到合适时机执行的消息,那么就会执行后序的逻辑;
  • 到了这一步,证明消息队列中并没有合适时机的消息可以执行,那么可以执行一些IdleHandler中的逻辑。后文再讲。

消息如何添加

这个其实在Handler中就已经介绍过了,就是通过post和send那两类进行添加,当然也可以通过消息队列直接添加同步屏障消息。那么下面就介绍下消息添加进消息队列的源码,enqueueMessage(Message msg, long when)

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        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;//mBlocked在next()方法中被赋值
        } 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;
}

其实上面这段代码,里面主要就是做了两件事:

  • 将Message插入到队列中;
  • 根据插入的位置来判断需不需要唤醒线程。
    • 如果插到链表的头部,那么需要根据mBlocked的状态决定需不需要唤醒,mBlocked会在next()方法中,当符合阻塞条件的时候置为true;
    • 如果插到链表的中间,一般是不需要唤醒的,除非:needWake = mBlocked && p.target == null && msg.isAsynchronous();

IdleHandler

public static interface IdleHandler {
    boolean queueIdle();
}

是一个简单的接口,在线程空闲的时候执行。那么何谓线程空闲呢?就是在当前线程的消息队列为空或者消息队列中的消息还都没有到可执行的时机,这个时候为了让线程不空闲,可以执行一些任务,这就是这个接口的作用。
那么应用场景有哪些呢?可以看腾讯Bugly出品的这篇文章,你知道android的MessageQueue.IdleHandler吗?,里面提出了两个应用场景。

  • 提供一个android没有的生命周期回调时机,在程序绘制完成时回调
  • 可以结合HandlerThread, 用于单线程消息通知器

参考资料

Android Handler机制4之Looper与Handler简介系列文章
【源码分析】关于MessageQueue,这一篇就够了!
ThreadLocal原理深入解析
Linux IO模式及 select、poll、epoll详解
你知道android的MessageQueue.IdleHandler吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值