Android的Handler是Android應(yīng)用層App運(yùn)行的核心,App除了InputEventReceiver外,其他的代碼運(yùn)行,比如生命周期,UI繪制都是被封裝成了Message消息的形式被Looper循環(huán)處理的。消息循環(huán)的休眠喚醒是依賴linux的epoll機(jī)制實(shí)現(xiàn)的。最終效果就是app在有任務(wù)的時候被喚醒執(zhí)行任務(wù),沒有任務(wù)的時候就休眠,App不會在沒有任務(wù)的時候空轉(zhuǎn)CPU消耗資源。
上面的不明白的可以參考下其他文章。
好,現(xiàn)在明白了Handler的整個原理,那如果讓我們自己來實(shí)現(xiàn)一個類似Android的Handler的框架結(jié)構(gòu),應(yīng)該怎么實(shí)現(xiàn)呢?
首先一定會有一個數(shù)據(jù)結(jié)構(gòu)來存儲所有需要處理的消息,然后每處理一個消息就把消息從隊(duì)列移除。所以這里需要用隊(duì)列。定義一個Queue。
private final Queue<Runnable> mQueue = new LinkedList<>();
private void addRunnable(Runnable runnable) {
mQueue.add(runnable);
}
public Runnable getRunnable(){
if (!mQueue.isEmpty()) {
return mQueue.poll();
}
return null;
}
然后會有一個執(zhí)行代碼的線程,肯定會有一個死循環(huán)不斷從queue里拿Runnable出來。然后運(yùn)行runnable。
while (true) {
Runnable runnable = getRunnable();
if (runnable != null) {
runnable.run();
}
}
再梳理下思路。Queue保存所有需要處理的任務(wù),我們這里任務(wù)先用Runnable表示。可以在任意一個線程調(diào)用Queue的add方法來添加Runnable。然后另外有個線程里是下面這個while(true),不斷從Queue里取runable來執(zhí)行。
好,這里突然發(fā)現(xiàn),添加runnable和運(yùn)行runnable涉及到跨線程了。所以這里需要處理下線程安全問題。所以把代碼改造成下面這樣。
private static final Queue<Runnable> mQueue = new LinkedList<>();
private static void addRunnable(Runnable runnable) {
synchronized (mQueue) {
mQueue.add(runnable);
}
}
private static Runnable getRunnable() {
synchronized (mQueue) {
if (!mQueue.isEmpty()) {
return mQueue.poll();
}
}
return null;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("創(chuàng)建Runnable的線程id = " + Thread.currentThread().getId());
addRunnable(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行Runnable的線程id = " + Thread.currentThread().getId());
}
});
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
Runnable runnable = getRunnable();
if (runnable != null) {
runnable.run();
}
}
}
運(yùn)行上面的代碼控制臺會打?。?/p>
創(chuàng)建Runnable的線程id = 11
執(zhí)行Runnable的線程id = 1
添加了線程同步,這里不會因?yàn)榭缇€程導(dǎo)致的數(shù)據(jù)同步問題了。再梳理下思路,線程11向queue addRunnable,這時候假如有個其他線程正在add或者get,也不要緊,synchronized會自動幫我們處理線程同步,保證add一定不會和其他線程發(fā)生沖突。不會導(dǎo)致add丟失。這時候add操作是在線程1完成的。然后線程1無限getRunnable,拿到后run執(zhí)行runnable。
這么想似乎勉強(qiáng)實(shí)現(xiàn)了消息隊(duì)列,也實(shí)現(xiàn)了runnable在其他線程創(chuàng)建,然后被線程1執(zhí)行。
但是一運(yùn)行就會發(fā)現(xiàn)一個嚴(yán)重的問題。因?yàn)榫€程1是個while(true),而且循環(huán)體內(nèi)沒有任何線程休眠,這會導(dǎo)致線程1,即使在沒有runnable需要執(zhí)行的時候,依然在不斷從queue里取數(shù)據(jù)出來,不斷的取,無限消耗CPU資源。這肯定是不行的。硬件資源都給這哥們用來空跑了。
那我們有沒有什么辦法能讓線程1智能一點(diǎn),讓線程1能在沒有runnable的時候線程休眠不消耗CPU,然后等有任務(wù)來了,再喚醒去執(zhí)行任務(wù),讓CPU資源只用來處理任務(wù),而不是空轉(zhuǎn)?
關(guān)鍵點(diǎn)來了,怎么讓線程1知道自己什么時候休眠什么時候喚醒呢?Android里這里利用的是Linux的epoll機(jī)制來實(shí)現(xiàn)的。效果就是線程1沒有任務(wù)了就休眠不消耗資源,任務(wù)來了,被喚醒去執(zhí)行任務(wù)。
Java里Object有兩個方法final native方法wait和notifyAll,如果用過,熟悉這兩個方法的話,這里就有思路了。當(dāng)線程1沒有任務(wù)的時候進(jìn)入wait不再消耗cpu,然后其他任意線程addRunnable的時候notifyAll喚醒線程1,然后線程1喚醒執(zhí)行runnable,然后再runnable執(zhí)行完成之后再次進(jìn)入wait狀態(tài)。
我們把上面代碼改造成下面這樣:
private static final Queue<Runnable> mQueue = new LinkedList<>();
private static void addRunnable(Runnable runnable) {
synchronized (mQueue) {
mQueue.notifyAll();
mQueue.add(runnable);
}
}
private static Runnable getRunnable() {
synchronized (mQueue) {
if (!mQueue.isEmpty()) {
return mQueue.poll();
} else {
try {
mQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return null;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("創(chuàng)建Runnable的線程id = " + Thread.currentThread().getId());
addRunnable(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行Runnable的線程id = " + Thread.currentThread().getId());
}
});
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int index = 0;
while (true) {
System.out.println("第" + (++index) + "次循環(huán)");
Runnable runnable = getRunnable();
if (runnable != null) {
runnable.run();
}
}
}
運(yùn)行結(jié)果:
創(chuàng)建Runnable的線程id = 11
第1次循環(huán)
執(zhí)行Runnable的線程id = 1
第2次循環(huán)
循環(huán)第一次執(zhí)行任務(wù),循環(huán)第二次就掛起等待新任務(wù)到來。到這里基本上Handler的基礎(chǔ)東西都有了。剩下的我們可以仿造AndroidHandler的Api重新包裝一個Handler-Message-Looper是非常容易的。下面的就不多解釋了,可以直接參考這里的源碼。
https://github.com/aesean/MessageQueue