安卓Handler机制学习笔记附面试问题

基础知识

  • Handler 机制相关的概念包括 Handler、Message 、Message Queue、Looper具体如下图


    相关的概念

    核心方法

接下来我们将四个相关的类一个个按着源码来看一遍

Message

  • Message是信息传递的载体里面没有太多逻辑就是包括一些信息传递机制里面必须要用到的属性主要看其构造函数的源码
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();
    }

    /*package*/ Handler target;
    /*package*/ Runnable callback;

    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;
        return m;
    }
  • 可以看出来是有一个Message池的,可以避免过多创建对象,造成系统浪费
  • 其中最重要的两个属性是target和callback分别是Handler和Runnable类型的其作用我们后面会讲到的

Handler

它在消息传递过程中扮演着重要的角色,是消息的主要处理者,说白了,就是发消息,收消息,最终处理消息其主要属性包括

  • 当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper()

  • Looper的消息队列赋值给mQueue:mQueue = mLooper.mQueue

  • Callback 来处理消息回调:mCallback = callback

注意点

  • Handler有且只能绑定一个线程的Looper
  • Handler的消息是发送给Looper的消息队列MessageQueue,需要等待处理
  • 调用Handler构造函数前要先构造Looperhe 和Message Queue,在主线程中可以直接调用是因为创建主线程的时候会自动调用Looper.prepareMainLooper和Looper.prepare,而其他线程是没有这一步的需要前创建Looper和Message Queue

以上是handler创建部分

sendMessage()

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(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;  //获得当前的消息队列
        if (queue == null) {   //若是在创建Handler时没有指定Looper,就不会有对应的消息队列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(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;   //这个target就是前面我们说到过的
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最终调用sendMessageAtTime()这个方法,此方法注意点有两个

  • msg.target = this 将Message的目标handler设置为本身
  • queue.enqueueMessage(msg, uptimeMillis)将这个message加入到当前的目标消息队列

以上是handler发送消息的部分

dispatchMessage()

前面说了消息的发送,交给Looper等待处理,处理完后会重新通知Handler处理,那么,是怎样通知Handler处理消息的呢?秘密就在dispatchMessage()这个方法中

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    /**
     * 子类必须实现这个来接收消息
     */
    public void handleMessage(Message msg) {
    }

当Looper处理完Message后,会使用到Message的target,即上面说到的target,即发送消息的那个Handler,Looper会调用Handler的dispatchMessage()方法分发消息,所以前面在enqueueMessage()发送消息的时候,为什么非得指明Message的target就是这个道理。

回到dispatchMessage()这个方法:
1、首先会判断Message的callback是否为空,此处的callback就是前面我们在Message中说到的,在静态方法创建Message时,可以指定的callback,若不为空,则将结果回调到callback中;
2、若Handler的mCallback 不为空,也一样的道理。
3、平时我们都没有传入这个callback,而是直接实现handleMessage()这个方法,在这个方法中处理更新UI任务。

以上就是Handler发送和接收消息的基本过程:把消息发送到队列—>然后喝茶等待—>接收消息—>分发消息—>在回调中处理。

MessageQueue

前面我们知道,Handler发送消息会调用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;
            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

Looper在Handler机制中扮演着关键的一环,他是循环处理消息的发动机),它不断的从消息队列中取出的消息,处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环。

loop( )的源码

public static void loop() {
        final Looper me = myLooper();   //获得当前的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        final MessageQueue queue = me.mQueue;  //获取当前Looper的消息队列
        //......

        for (;;) {
            Message msg = queue.next();  //取出队头的消息
            if (msg == null) {
                // 如果消息为空,则跳过,继续执行下一个message
                return;
            }
            //......
            try {
                msg.target.dispatchMessage(msg);
                //......
            } finally {
               //......
            }
           //......
            msg.recycleUnchecked();  //回收可能正在使用的消息
        }
    }

总结

消息传递机制简图

image.png

面试问题

  1. Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?
    对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
    主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源

  2. 主线程的消息循环机制是什么?
    Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出了。 从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
    thread.attach(false)方法函数中便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。「Activity 启动过程」
    比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
    再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。
    主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程
    Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。
    ActivityThread通过ApplicationThread和AMS进行进程间通讯,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是。主线程的消息循环模型

  3. Handler 是如何能够线程切换
    这里再引申出Handler的一些小知识点。 Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。
    那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

  4. 子线程有哪些更新UI的方法

  • 使用runOnUiThread()
public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

如果是在主线程,直接运行,不是进行mhandler.post()

  • post

new Thread() {  
    public void run() {
        button.post(new Runnable(){  
            @Override  
            public void run() {  
                button.setText("sssssss");   
            }  
        }); 
    }
}

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action); //一般情况走这里
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

实际还是调用handler

  • AsyncTask
    通过Handler

  • Handler+handleMessage

  1. 系统为什么不允许在子线程中访问UI?(摘自《Android开发艺术探索》) 这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个: ①首先加上锁机制会让UI访问的逻辑变得复杂 ②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。 所以最简单且高效的方法就是采用单线程模型来处理UI操作。

6.Handler使用引起的内存泄漏原因以及解决办法
原因

  • 当一个android应用程序启动的时候,frameworks会自动为这个应用程序在主线程创建一个Looper对象。这个被创建的Looper对象也有它主要的工作,它主要的工作就是不断地处理消息队列中的消息对象。在android应用程序中,所有主要的框架事件(例如Activity的生命周期方法,按钮的点击事件等等)都包含在消息对象里面,然后被添加到Looper要处理的消息队列中,主线程的Looper一直存在于整个应用程序的生命周期中。
    当一个Handler在主线程中被初始化。那它就一直都和Looper的消息队列相关联着。当消息被发送到Looper关联的消息队列的时候,会持有一个Handler的引用,以便于当Looper处理消息的时候,框架可以调用Handler的handleMessage(Message msg)。
    在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。静态内部类则不会持有外部类的引用。
    当Activity被finished掉的时候,被延时的消息会在被处理之前存在于主线程的消息队列中十分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的MainActivity的引用。这些引用会一直保持到该消息被处理,从而阻止了MainActivity被垃圾回收器回收。因此这就导致了MainActivity无法被回收,进而导致MainActivity持有的很多资源都无法回收,这就是我们常说的内存泄漏。
    解决方法:
    1 有延时消息,要在Activity销毁的时候移除Messages
    2 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。

你可能感兴趣的:(安卓Handler机制学习笔记附面试问题)