Android的Handler是Android應(yīng)用層App運行的核心乞巧,App除了InputEventReceiver外,其他的代碼運行摊鸡,比如生命周期绽媒,UI繪制都是被封裝成了Message消息的形式被Looper循環(huán)處理的。消息循環(huán)的休眠喚醒是依賴linux的epoll機(jī)制實現(xiàn)的免猾。最終效果就是app在有任務(wù)的時候被喚醒執(zhí)行任務(wù)是辕,沒有任務(wù)的時候就休眠,App不會在沒有任務(wù)的時候空轉(zhuǎn)CPU消耗資源猎提。
上面的不明白的可以參考下其他文章获三。
好,現(xiàn)在明白了Handler的整個原理锨苏,那如果讓我們自己來實現(xiàn)一個類似Android的Handler的框架結(jié)構(gòu)疙教,應(yīng)該怎么實現(xiàn)呢?
首先一定會有一個數(shù)據(jù)結(jié)構(gòu)來存儲所有需要處理的消息伞租,然后每處理一個消息就把消息從隊列移除贞谓。所以這里需要用隊列。定義一個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出來。然后運行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和運行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();
}
}
}
運行上面的代碼控制臺會打釉猎堋:
創(chuàng)建Runnable的線程id = 11
執(zhí)行Runnable的線程id = 1
添加了線程同步所森,這里不會因為跨線程導(dǎo)致的數(shù)據(jù)同步問題了。再梳理下思路夯接,線程11向queue addRunnable焕济,這時候假如有個其他線程正在add或者get,也不要緊盔几,synchronized會自動幫我們處理線程同步晴弃,保證add一定不會和其他線程發(fā)生沖突。不會導(dǎo)致add丟失逊拍。這時候add操作是在線程1完成的上鞠。然后線程1無限getRunnable,拿到后run執(zhí)行runnable芯丧。
這么想似乎勉強(qiáng)實現(xiàn)了消息隊列旗国,也實現(xiàn)了runnable在其他線程創(chuàng)建,然后被線程1執(zhí)行注整。
但是一運行就會發(fā)現(xiàn)一個嚴(yán)重的問題。因為線程1是個while(true)度硝,而且循環(huán)體內(nèi)沒有任何線程休眠肿轨,這會導(dǎo)致線程1,即使在沒有runnable需要執(zhí)行的時候蕊程,依然在不斷從queue里取數(shù)據(jù)出來椒袍,不斷的取,無限消耗CPU資源藻茂。這肯定是不行的驹暑。硬件資源都給這哥們用來空跑了。
那我們有沒有什么辦法能讓線程1智能一點辨赐,讓線程1能在沒有runnable的時候線程休眠不消耗CPU优俘,然后等有任務(wù)來了,再喚醒去執(zhí)行任務(wù)掀序,讓CPU資源只用來處理任務(wù)帆焕,而不是空轉(zhuǎn)?
關(guān)鍵點來了不恭,怎么讓線程1知道自己什么時候休眠什么時候喚醒呢叶雹?Android里這里利用的是Linux的epoll機(jī)制來實現(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();
}
}
}
運行結(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