Android核心知识——消息机制

Handler简介

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

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

  • 在不一样线程中执行任务。git

  • 执行定时任务。github

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使用大体分为三种状况:数组

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

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

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

下面咱们结合实际案例或使用场景来看一下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<HandlerSampleActivity> 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<String> 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类型的数据,又声明一个普通的String变量,初始值为Main Thread,其目的是为了与ThreadLocal<String>造成对比。在主线程中使用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<T>指定的类型,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<ThreadLocal<?>> {
        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核心算法了。有兴趣的能够点这里

上图描述了咱们以前的演示代码,每一个线程中都有一个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<Looper> sThreadLocal = new ThreadLocal<Looper>();
    // 主线程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);
}
复制代码

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

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

Android的消息机制是很重要的,不管是平常工做,仍是源码学习,或使面试都是离不开它的。因此真正掌握消息机制是颇有必要的。

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

邮箱:eonliu1024@gmail.com

Github: github.com/Eon-Liu

相关文章
相关标签/搜索