Android进阶之路——Handler机制

Handler简介

Handler在日常开发中或多或少的都用过,在Android开发中是一个比较重要的知识点,希望通过这篇文章会使你对Handler有更全面的理解。

Hanlder设计的初衷或用途主要有两点:

  • 在不同线程中执行任务。

  • 执行定时任务。

Handler基本使用方式

下面代码展示了Handler使用的基本流程。

// 定义一个消息标识符
final int MESSAGE_WHAT_TEST = 100;

// 1.创建一个Handler对象。
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 2. 重写handleMessage方法处理发送过来的Message

        // 判断Message类型
        if(msg.what == MESSAGE_WHAT_TEST) {
            // 接收Message中的数据
            String data = (String) msg.obj;
            // 展示数据
            textView.setText(data);
        }
    }
};

// 3. 新建一个Message对象承载要传输的数据。
Message msg = new Message();
// 4. 给Message设置一个标识符,这个标识符可以用来区分这个Message是用来干什么的。
msg.what = MESSAGE_WHAT_TEST;
msg.obj = "这里可以是任何类型的数据对象";
// 5. 将Message发送给Handler处理
handler.sendMessage(msg);

上面代码展示了使用Handler的基本流程。但是编码还是存在一些缺陷。例如Handler可能会导致Activity内存泄漏、使用优先使用Message.obtain();而不是new Message();等。这些问题在后面你会找到答案。

这里我们Hanlder使用大致分为三种情况:

  • 子线程发送消息到主线程。

  • 主线程发送消息到子线程。

  • 执行定时任务、或周期性行任务。

下面我们结合实际案例或使用场景来看一下Handler是如何解决问题的。

子线程发送消息到主线程。

这种情况的典型案例就是子线程请求数据,主线程更新UI。这也是最广为人知的使用场景了,因为Android系统推荐UI相关操作在主线程中进行。在ViewRootImpl.javacheckThread方法中进行了线程校验。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

由于这一点限制,导致必须在主线程中进行UI操作。另外Android不推荐在主线程中做耗时操作,耗时操作会导致UI卡顿甚至程序无法响应,也就是ANR

有同学就有疑问了,为什么不在子线程中更新UI呢?

因为Android中的View不是线程安全的,多线程并发的操作UI可能会出现不可预期的状态。子线程在特定情况下也是可以完成UI更新的,只不过不推荐这么做。

有同学又问了,那为什么不把View设计成线程安全的呢?

从设计上看,View即处理界面展示、又处理线程安全不符合单一职责原则。从效率上看,线程安全需要用到锁机制会导致View的设计复杂,某些情况会导致线程阻塞等问题。

还有同学问。。。等会,先别问了,继续往后看或许你的问题就有答案了。

因为有了上述的限制,在日常开发的过程中大家都是采用开启一个子线程来请求数据,然后使用Handler将数据发送到主线程,主线程收到数据后更新UI。

// 在主线程中创建一个Handler
final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 在主线程中执行
        // 接收网络请求的数据
        String data = (String) msg.obj;
        // 更新UI,将数据展示到页面上
        textView.setText(data);
    }
};

// 新建一个子线程请求数据
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // 模拟网络请求数据耗时
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 假设这是来自服务器的数据
        String data = "我是来自远方的数据";
        // 新建一个Message对象来承载数据
        Message message = Message.obtain();
        // 设置一个flag来标识这个Message的类型
        message.arg1 = 1;
        // 将数据放入Message中
        message.obj = data;
        // 将Message发送到主线程中处理
        handler.sendMessage(message);
    }
}).start();

首先我们在主线程中创建一个Handler,因为Handler是在主线程中创建的,所以handleMessage方法则会在主线程中执行。然后新建一个子线程(工作线程)去请求网络数据,请求数据成功之后使用Message包装数据,使用handler.sendMessage(message)将数据发送给Handler处理。之后在handleMessage方法中就会收到子线程的消息,然后将数据展示在页面上。这样就完成了子线程请求数据,主线程展示数据的需求了。

主线程发送消息到子线程。

这种情况的实际应用场景不太好找,我们就用简单的代码来说明一下这种情况有哪些注意事项吧。

new Thread(new Runnable() {
    @Override
    public void run() {
        // 初始化子线程Looper
        Looper.prepare();
        // 在子线程中创建一个Handler对象。
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 重写handleMessage方法处理发送过来的Message

                // 判断Message类型
                if (msg.what == MESSAGE_WHAT_TEST) {
                    // 接收Message中的数据
                    String data = (String) msg.obj;
                    Log.d(TAG, data);
                    // 可以更新UI,但是强烈不推荐这么做
                    // textView.setText(data);
                }
            }
        };
        // 开启Looper,发现有消息就会交给handler处理
        Looper.loop();
    }
}).start();

// 点击按钮发送一条消息到子线程
sendMessageButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 新建一个Message对象承载要传输的数据。
        Message msg = Message.obtain();
        // 给Message设置一个标识符,这个标识符可以用来区分这个Message是用来干什么的。
        msg.what = MESSAGE_WHAT_TEST;
        msg.obj = "这是来自主线程的数据";
        // 将Message发送给Handler处理
        handler.sendMessage(msg);
    }
});

主线程发送消息到子线程说明要在子线程中处理消息,所以Handler是在子线程中创建的。另外在子线程中处理消息要使用Looper.prepare();Looper.loop();创建并开启消息循环机制,并在这两行代码之间完成Handler的创建和处理。这里的编码顺序一定要牢记,否则消息是不会被handleMessage处理的。

在子线程使用handler,当handler不再需要发送和处理消息需要使用Looper.myLooper().quit();Looper.myLooper().quitSafely();停止消息轮询。因为Looper.loop()是一个死循环,如果不主动结束它会一直执行,子线程也就一直执行。

当我们点击sendMessageButton的时候就会向子线程发送一条消息。在子线程handleMessage中对消息进行了处理,可以看到textView.setText(data);被注释了,这句代码是可以正常运行的,收到的数据会展示到textView中,但是强烈不推荐这样做。

执行定时任务、或周期性行任务。

Handler提供了延迟发送消息的API可以用来实现定时任务或周期性任务。

handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        Log.d(TAG, "三秒打印一条日志");
        // 延迟三秒发送一条空消息
        handler.sendEmptyMessageDelayed(0, 3 * 1000);
    }
};

// 发送第一条空消息
handler.sendEmptyMessage(0);

sendEmptyMessageDelayed第二参数指定了延迟时间,上面我们设置延迟3000毫秒发送一条空消息,然后handler会收到这条空消息交给handleMessage处理,这样sendEmptyMessageDelayed就会再次被执行,最终形成了定时执行的效果。

Handler使用注意事项

Handler导致内存泄漏问题

在最新版的Android Studio中编译上面代码时,编译器会提示我们This Handler class should be static or leaks might occur (anonymous android.os.Handler)编译器建议我们将Handler设置成静态的内部类,否则可能会导致内存泄漏。

这是因为Java内部类会持有外部类的强引用,上面我们创建Handler使用的都是匿名内部类的形式,所以Handler内部会持有外部类(Activity)的强引用,然后Message会持有handler的强引用,Message会被放到MessageQueue中等待被处理,如果这时Activity退出了但是Message还没有被处理就会导致Activity不能被GC释放一直停留在内存中。也就形成了Activity的内存泄漏。

解决这个问题也很简单,编译器给出了解决建议。

// 将Handler声明为静态的内部类
static class H extends Handler {
    // 使用WeakReference(弱引用)保存Activity引用。
    private WeakReference wr;

    public H(HandlerSampleActivity activity) {
        wr = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 判断Activity是否被回收
        if(wr.get() != null) {
            // 如果Activity没被回收的话就调用Activity的方法
            wr.get().doWork();
        }
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // activity销毁时移除handler所有未被处理的callback和Message
    handler.removeCallbacksAndMessages(null);
}

因为Java的静态内部类不会持有外部类的引用,所以我们把Handler声明为静态的内部类,如果需要使用Activity的引用时需要使用WeakReference对Activity进行处理,WeakReference是弱引用,当GC发生时被持有的对象会被回收。

另外在Activity的onDestroy()中调用handler.removeCallbacksAndMessages(null);参数为null表示移除handler中所有未被处理的callbackMessage,这样就不会出现Activity内存泄漏的情况了。

优先使用Message.obtain()创建Message

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

Message.obtain()的源码可以看出Message中有一个全局的消息池——sPool。使用Message.obtain()方法会优先在消息池——sPool中线获取Message,如果没有可用消息才会调用new Message()创建一个新消息。这样做的优点就是可以避免不必要的内存分配。

上面的演示代码可以在Github中找到演示代码Gihub地址

Android消息机制在面试中是常客,学习消息机制阅读源码是必须的,消息机制也就是Handler的运行机制。学习Handler运行机制主要涉及四个类。

Handler:发送或处理消息。

MessageQueue:消息队列,用来保存消息。

Looper:从MessageQueue获取消息,交给Handler处理。

ThreadLocal:负责切换线程。

ThreadLocal源码

为了能更好的理解后面的内容我们需要先讲一下ThreadLocalThreadLocal是一个泛型类,它保存一个泛型类型的数据。ThreadLocal使用场景并不多,平时用的不多。我们先通过一段代码看看ThreadLocal能干什么。

// 创建一个ThreadLocal对象用来保存一个String
private ThreadLocal threadLocal = new ThreadLocal<>();
// 创建一个普通的String变量
private String localString = "Main Thread";

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.ativity_sample);

    // 修改threadLocal的String
    threadLocal.set("Main Thread");
    Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
    Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());

    new Thread("Thread-1") {
        @Override
        public void run() {
            // 修改threadLocal的String
            threadLocal.set("Thread-1");
            localString = "Thread-1";
            Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
            Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());
        }
    }.start();

    new Thread("Thread-2") {
        @Override
        public void run() {
            Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
            Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());
        }
    }.start();
}

输出日志:

D/ThreadLocal: threadLocal: Main Thread, thread: main
D/ThreadLocal: localString: Main Thread, thread: main
D/ThreadLocal: threadLocal: Thread-1, thread: Thread-1
D/ThreadLocal: localString: Thread-1, thread: Thread-1
D/ThreadLocal: threadLocal: null, thread: Thread-2
D/ThreadLocal: localString: Thread-1, thread: Thread-2

代码中创建一个ThreadLocal对象,它可以保存一个String类型的数据,又声明一个普通的String变量,初始值为Main Thread,其目的是为了与ThreadLocal形成对比。在主线程中使用threadLocal.set();threadLocal设置一个新值。然后打印两个变量的值和当前的线程名字(threadLocal.get()可以获取到threadLocal.set();的值)。下面有新建了两个线程,在Thread-1中分别对两个变量进行赋值并打印其值和线程名字。在Thread-2中直接打印两个变量的值和线程名字。

从输出日志可以看出前两次的打印结果两个变量的值是相同的,结合代码也很好理解。需要说明的是在Thread-2线程中打印的结果出现了不同。打印localString的值是Thread-1,这是因为localString最后一次赋值就是Thread-1,这个也很好理解,奇怪的是threadLocal最后打印的是null,而不是Thread-1。这就是ThreadLocal与普通变量的不同之处了。

ThreadLocal会为每个线程创建一个副本,它们互补干扰。所以才会有上面的结果。我们通过源码来分析一下它是怎么做到的。

我们从ThreadLocalset()方法开始看。

// 泛型T就是ThreadLocal指定的类型,value就是要保存的值,在多个线程中这个值相互独立,不会被其他线程所修改
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程中的threadLocals,threadLocals是
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 如果threadLocals不为空则将ThreadLocal和value保存起来
        map.set(this, value);
    else
        // 如果threadLocals为null则创建一个新的ThreadLocalMap来保存ThreadLocal和value
        createMap(t, value);
}

set方法中先获取当前线程,然后在通过getMap方法获取当前线程中的threadLocalsThread类中有一个成员变量threadLocals

public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

如果threadLocals为空则调用createMap(t, value);创建一个新的ThreadLocalMap保存ThreadLocalvalue

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

如果threadLocals不为空则调用map.set(this, value);保存ThreadLocalvalue

private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 计算ThreadLocal在table中的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

这个方法的算法比较复杂,其重要的一点就是通过int i = key.threadLocalHashCode & (len-1);计算出ThreadLocal应该保存在table中的哪个位置(在ThreadLocal中称之为槽位)。table的声明如下:

static class ThreadLocalMap {
    static class Entry extends WeakReference> {
        Object value;
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
    private Entry[] table;
}

可以看出ThreadLocalvalue以键值对的形式保存在table中指定位置,这个位置就是``int i = key.threadLocalHashCode & (len-1);计算出来的,并且他是唯一的,不同的ThreadLocal计算出来的值是不同的,不会出现冲突。如果threadLocals为空的情况在也会使用相同的算法来计算ThreadLocal位置的。

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    // 计算ThreadLocal在table中的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 将ThreadLocal和value保存到table中
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

保存的逻辑梳理清楚了,在来看看看get()方法。

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // threadLocals不为空则查找ThreadLocal对应的Entry(键值对),key:ThreadLocal,value:之前保存的值。
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果没找到返回一个初始值,默认是null
    return setInitialValue();
}

get()方法相对简单,我们主要关注map.getEntry(this);,看看如何取出之前保存的值。

private Entry getEntry(ThreadLocal key) {
    // 计算ThreadLocal在table中的位置
    int i = key.threadLocalHashCode & (table.length - 1);
    // 取出Entry
    Entry e = table[i];
    if (e != null && e.get() == key)
        // 如果e不为空并且是当前的ThreadLocal则返回Entry
        return e;
    else
        // 如果没找到对应的ThreadLocal
        return getEntryAfterMiss(key, i, e);
}

可以看到int i = key.threadLocalHashCode & (table.length - 1);又出现了,这也是ThreadLocal核心算法了。有兴趣的可以点这里。

Android进阶之路——Handler机制_第1张图片
ThreadLocal.png

上图描述了我们之前的演示代码,每个线程中都有一个ThreadLocalMap类型的threadLocals成员,其中包含了一个默认长度为16的Entry类型数组table。当在线程中调用threadLocal.set(value)就会把value存到table指定位置中,这个位置就是通过int i = key.threadLocalHashCode & (table.length - 1);算出来的,其实还有一些寻找槽位的逻辑,这里就不说明了。Thread-2线程没有调用threadLocal.set(value)所以在调用threadLocal.get()时会调用setInitialValue()初始化一个默认值,也就是null。而在MainThreadThread-1中调用threadLocal.get()就会得到之前set的值。

总结:

  • 多个线程调用set互影响,每个线程中都会保存一份副本。
  • 每个线程中只能get到当前线程的副本值。
  • 不调用set获取的值是null,可以通过重写ThreadLocalinitialValue()方法改变默认值。

Message源码

Message的源码相对简单,下面给出注释。

public final class Message implements Parcelable {
    // 给消息编码,以便接受者能区分消息是什么类型或者用途。
    public int what;
    // 携带一个int类型数据
    public int arg1;
    // 携带一个int类型数据
    public int arg2;
    // 携带一个Object类型数据
    public Object obj;
    // 消息被发送的时间
    /*package*/ long when;
    // 用来携带数据的Bundle,如果数据简单有限考虑使用arg1/arg2和obj。
    /*package*/ Bundle data;
    // 发送消息的Handler引用。
    /*package*/ Handler target;
    // 用来处理消息的回调
    /*package*/ Runnable callback;
    // 消息队列是以单链表形式存在的,next用于保存下一节点
    /*package*/ Message next;
    // 消息池
    private static Message sPool;
    // 消息池中当前有多少个消息
    private static int sPoolSize = 0;
    // 消息池最多保存50个Message
    private static final int MAX_POOL_SIZE = 50;

    // 试图从消息池中取一个消息,如果消息池是空的则new一个新消息。
    // 新建Message是优先使用obtain系列方法,而不是使用new的方式。
    // 其他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
    public static Message obtain(Message orig)
    // 通过obtain()新建一个消息,把参数h赋值给Message的target。
    public static Message obtain(Handler h)
    // 通过obtain()新建一个消息,把参数h赋值给Message的target。
    // 把参数callback赋值给Message的callback。
    // callback可以用来处理消息,与handleMessage功能一样
    public static Message obtain(Handler h, Runnable callback)
    // 通过obtain()新建一个消息,把参数h赋值给Message的target。
    // 把参数what赋值给Message的what。    
    public static Message obtain(Handler h, int what)
    // 通过obtain()新建一个消息,把参数h赋值给Message的target。
    // 把参数what赋值给Message的what。
    // 把参数obj赋值给Message的obj。
    public static Message obtain(Handler h, int what, Object obj)
    public static Message obtain(Handler h, int what, int arg1, int arg2)
    public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)
    
    ...
}

总结:

  • 消息池是以单链表的形式存在的,next成员保存链表中下一节点Message的引用。
  • target成员保存发送Message或处理Message的Handler引用。
  • 优先使用obtain系列方法获取Message对象,可以避免不必要的内存分配。

MessageQueue源码

MessageQueue的主要用途就管理Message队列。(源码是通过单链表形式实现队列操作)

// 将msg插入消息队列中
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;
        // 将msg插入到队列中
        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;
}

enqueueMessage是将Message插入到链表中的方法。

// 读取消息
Message next() {
    ...
    // 无限循环读取消息,如果没有就会一直等待,如果有新消息立即返回
    for (;;) {
        ...
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            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;
            }

            ...
        }

        ...
    }
}

next()方法用一个死循环读取消息队列中的消息,如果有新消息就会立即返回新消息,如果没有新消息就会一直等待。当消息队列为空的状态next方法还会阻塞当前线程。

// 停止消息循环
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);
    }
}

总结:

  • 消息队列是用单链表实现的。
  • next()方法会阻塞线程,不用的时候调用quit()方法退出。
  • quit()有两种方式。1. 处理完剩余的消息在退出。2.直接退出。

Looper源码

默认线程不具备消息循环能力,需要使用Looper.prepare()Looper.loop()开启消息循环。Looper通过MessageQueue监控新消息,如果发现新消息则把消息交给Handler处理或者调用callback处理。

public final class Looper {
    // 使用ThreadLocal保证当前线程的Looper不受干扰
    static final ThreadLocal sThreadLocal = new ThreadLocal();
    // 主线程Looper
    private static Looper sMainLooper;  // guarded by Looper.class
    // 消息队列
    final MessageQueue mQueue;
    // 当前线程
    final Thread mThread;
    
    // 构造函数
    private Looper(boolean quitAllowed) {
        // 创建一个消息队列
        mQueue = new MessageQueue(quitAllowed);
        // 获取当前线程
        mThread = Thread.currentThread();
    }
    
    // 创建Looper
    public static void prepare() {
        prepare(true);
    }

    // 创建Looper
    private static void prepare(boolean quitAllowed) {
        // 如果重复调用prepare会报错。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 使用ThreadLocal保存工作线程的Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    // 创建主线程的Looper
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // sMainLooper保存主线程的Looper
            sMainLooper = myLooper();
        }
    }
}

因为线程默认没有消息循环能力,所以需要使用Looper.prepareLooper.loop()开启消息循环。主线程的消息循环是在ActivityThreadmain()方法中创建的。

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

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

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // 开启主线程消息循环
    Looper.loop();

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

处理消息一般都需要一个Handler,主线程的消息都是由ActivityThread.H来处理的。

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;

    ....

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                // 应用退出时调用quit退出消息循环
                Looper.myLooper().quit();
                break;
                ...
    }    
}

H处理了系统重要组件的启动和停止等过程。在应用退出时调用了Looper.myLooper().quit();退出消息循环,上面我们讲到MessageQueuenext()是一个死循环,如果不主动退出那么就会一直运行,这会导致线程无法退出。主线程的消息循环退出由系统处理了,我们在子线程中使用Looper时一定要记得在不需要消息循环的时候主动退出消息循环。退出消息循环有两种方式。

// 直接退出,移除消息队列中所有消息
public void quit() {
    mQueue.quit(false);
}

// 将消息队列中的消息处理完在退出。
public void quitSafely() {
    mQueue.quit(true);
}

可以看到两种退出方式实际上就是上面MessageQueue中讲的两种退出方式。关于Looper的源码还差最终要的一个方法没看,那就是loop()方法。

// 开启当前线程的消息循环
public static void loop() {
    // 获取当前线程的Looper
    final Looper me = myLooper();
    // 如果之前没有调用Looper.prepare()创建Looper则会报错。
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 当前线程的消息队列
    final MessageQueue queue = me.mQueue;

    ...
    // 开启消息循环,(一个死循环)
    for (;;) {
        // 调用queue.next(),看看有没有新消息。再次提示queue.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可以切换线程的关键。
            // 我们在Handler源码一节详细讲解
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}

msg.target.dispatchMessage(msg);Handler可以切换线程处理消息的重点,这句代码一定要好好理解一下。

总结:

  • 调用Looper.prepare()创建Looper,调用Looper.loop()开启消息循环。
  • 系统ActivityThreadmain()方法中开启了主线程的消息循环,并在应用退出的时候使用Looper.myLooper().quit()退出了消息循环。
  • 多次调用Looper.prepare()会抛出Only one Looper may be created per thread异常。
  • 调用Looper.loop()之前没有调用Looper.prepare()会抛出No Looper; Looper.prepare() wasn't called on this thread.异常。
  • 在子线程中使用Looper要在不需要消息循环的时候调用Looper.quit()主动退出消息循环。否则主线程无法释放。

讲了这么多,最后的主角终于来了。

Handler源码

public Handler() {
    this(null, false);
}

// 指定消息循环的looper,默认是创建Handler线程的looper,也可以自己指定,
// 例如在子线程创建Hanlder,这里指定为Looper.getMainLooper(),
// 那么handlerMessage的处理消息的方法就会在主线程被执行。
// callback也是用来处理消息的,优先级高于handleMessage方法
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

...

@hide
public Handler(Callback callback, boolean async) {
    ...
    // 通过ThreadLocal获取当前线程的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        // 通常在子线程中没有调用Looper.prepare()创建Looper会报错
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    // 消息队列
    mQueue = mLooper.mQueue;
    // 用来处理Message的回到,优先级高于handleMessage方法
    mCallback = callback;
    // 是否是异步消息,默认是同步消息。结合MessageQueue的postSyncBarrier方法可以提高Message的优先级,可以优先得到处理。
    mAsynchronous = async;
}

先看一下Looper.myLooper()

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

myLooper()就是通过ThreadLocal获取当前线程的Looper对象。如果是在主线程创建Handler它就是sMainLooper,若果是在子线程它就是子线程中用Looper.prepare()创建的Looper。所以这里的mLooperHandler是在同一个线程中的。

  • 创建Handler默认使用当前线程的Looper。可以通过构造函数指定Looper
  • 在子线程中创建Handler之前需要使用Looper.prepare()创建Looper,否则报错。
  • 可以指定一个Callback用来处理Message
  • 有关于async参数系统并不推荐我们更改,有关方法已经被标记@hide

Handler创建完了,继续看看Handler是如何发送消息的。

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(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) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}

上面的send方法最终都是通过sendMessageAtTime处理的,除了send系列方法,Handler还有一系列的post方法。

public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean postDelayed(Runnable r, Object token, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}

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

post系列方法则是通过getPostMessage方法构造一个携带Runnable对象Message,最终也是通过enqueueMessage进行统一处理了。

ActivityrunOnUiThread()方法就是用的post

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        // 如果不是UI线程则将Runnable添加到主线程的消息队列等待执行。
        mHandler.post(action);
    } else {
        // 如果是UI线程则直接执行run方法
        action.run();
    }
}

所有的发送消息的方法最终都由enqueueMessage方法处理了。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 给Message中的target赋值,this就是用来发送和处理Message的handler对象。
    msg.target = this;
    // 是否是异步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 通过MessageQueue将消息插入消息队列中。
    return queue.enqueueMessage(msg, uptimeMillis);
}

enqueueMessage首先给msg.target赋值,this就是用来发送和处理Message的handler对象。最终调用MessageQueueenqueueMessage方法将消息插入消息队列中。消息被插入到消息队列中之后就会被MessageQueuenext方法发现,之后交给LooperLooper通过msg.target.dispatchMessage(msg);将消息交给Handler处理。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 如果Message的callback不为空则执行callback,这里的callback实际是一个Runnable
        handleCallback(msg);
    } else {
        // 如果msg没设置callback则判断handler的callback,这里的callback类型是Callback。
        if (mCallback != null) {
            // 在创建Handler的时候可以指定一个Callback参数。
            // 若果有callback则把消息交给callback处理,如果callback返回true则处理完成,
            // 否则在将消息交给handler的handleMessage方法处理
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 这就是我们重写的handleMessage方法,用来处理消息。
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    // 执行Runnable的run方法
    message.callback.run();
}

// 被我们重写的方法,用来处理消息
public void handleMessage(Message msg) {
}

// new Handler时候可以指定一个Callback,用来处理消息,优先级比handleMessage高。
public interface Callback {
    public boolean handleMessage(Message msg);
}

到这里有关消息机制的源码就分析的差不多了。我们把上面所讲的内从串联起来形成一张图。

Android进阶之路——Handler机制_第2张图片
Handler.png

因为HandlerLooperMessageQueue都是在主线程创建的,所以handler.sendMessage(message)所发送的消息也被插入到了主线程的消息队列中了,然后在交给主线程的msg.target.dispatchMessage(msg)分发处理。这样就完成了一条消息的处理。

Android的消息机制是很重要的,无论是日常工作,还是源码学习,或使面试都是离不开它的。所以真正掌握消息机制是很有必要的。

至此Android的消息机制就分析完了,文中有哪些不足之处欢迎到我的Github指正。

邮箱:[email protected]
Github: https://github.com/Eon-Liu

你可能感兴趣的:(Android进阶之路——Handler机制)