標籤:message loop android handler chromium on android
摘要:剛一開始接觸Chromium on Android時,就很好奇Chromium的主訊息迴圈是怎麼整合到Android應用程式中的。對於Android程式來說,一旦啟動,主線程就會有一個Java層的訊息迴圈處理使用者輸入事件等系統事件,而對Chromium來說,它有自己另一套訊息迴圈的實現,這個實現有哪些特點,又將如何無縫整合到Android Java層的訊息迴圈中去,正是本文所要討論的話題。
原創文章系列,轉載請註明原始出處為http://blog.csdn.net/hongbomin/article/details/41258469.
訊息迴圈和主訊息迴圈
訊息迴圈,或叫做事件迴圈,是非同步編程模型中一個重要的概念,用來處理單個線程中發生的非同步事件,這些非同步事件包括使用者輸入事件,系統事件,Timer以及線程間派發的非同步任務等。
Chromium系統將訊息迴圈抽象為MessageLoop類,規定每個線程最多隻能同時運行一個MessageLoop執行個體,MessageLoop提供了PostTask系列方法允許向任務隊列中添加新的非同步任務。當MessageLoop發現有新任務到達,它都會從輪詢自己的任務隊列並從按“先進先出”的方式執行任務,周而復始,直到收到MessageLoop::Quit訊息,訊息迴圈才會退出。
Chromium按照所需處理的非同步事件,將MessageLoop劃分為幾種不同的類型:
- TYPE_DEFAULT: 預設的訊息迴圈,只能處理定時器Timer和非同步任務;
- TYPE_UI:不但可以處理定時器Timer和非同步任務,還可以處理系統UI事件,主線程使用的就是該類型的MessageLoop,也就是主訊息迴圈;
- TYPE_IO: 支援非同步IO事件,Chromium所有處理IPC訊息的IO線程建立的都是該類型的MessageLoop;
- TYPE_JAVA: 專為Android平台設計的訊息迴圈類型,後端實現是Java層的訊息處理器,用來執行添加到MessageLoop中的任務,其行為與TYPE_UI類型差不多,但建立時不能使用主線程上的MessagePumpFactory 方法。註:該類型的MessageLoop與本文討論的訊息迴圈無關。
MessageLoop具體實現和平台相關,即使在相同的平台上,由於使用了不同的事件處理庫,其實現方式也有可能不同。Chromium將平台相關的實現封裝在MessagePump抽象類別中,類MessageLoop和MessagePump之間的關係如下所示:
MessagePump的具體實現提供了平台相關的非同步事件處理,而MessageLoop提供輪詢和調度非同步任務的基本架構,兩者通過MessagePump::Delegate抽象介面關聯起來。
MessagePump::Run的基本代碼骨架如下:
for (;;) { bool did_work =DoInternalWork(); if (should_quit_) break; did_work |= delegate_->DoWork(); if (should_quit_) break; TimeTicks next_time; did_work |=delegate_->DoDelayedWork(&next_time); if (should_quit_) break; if (did_work) continue; did_work =delegate_->DoIdleWork(); if (should_quit_) break; if (did_work) continue; WaitForWork();}上述程式碼片段中,有三點需要特別說明:
- MessagePump既要負責響應系統的非同步事件,又要給足夠的時間片段調度Delegate去執行非同步任務,所以MessagePump是以混合交錯的方式調用DoInternalWork,DoWork, DoDelayedWork和DoIdleWork,以保證任何一類任務不會因得不到執行而都發生“饑餓”現象;
- DoInternalWork和WaitForWork都是MessagePump具體實現的私人方法。DoInternalWork負責分發下一個UI事件或者是通知下一個IO完成事件,而WaitForWork會阻塞MessagePump::Run方法直到當前有任務需要執行;
- 每個MessagePump都有設定should_quit_標記位,一旦MessagePump::Quit被調用了,should_quit_將置為true, 每次MessagePump處理完一類任務時都要去檢查should_quit_標記,以決定是否繼續處理後續的任務,當發現should_quit_為true時,直接跳出for迴圈體。
嵌套的訊息迴圈(Nested MessageLoop)
如果把訊息迴圈比作是一個需要做很多事情的夢境,那麼嵌套的訊息迴圈就是“盜夢空間”了,從一個夢境進入另外一個夢境了。
簡單地說,嵌套的訊息迴圈就是當前訊息迴圈中因執行某個任務而進入另外一個訊息迴圈,並且當前的訊息迴圈被迫等待嵌套訊息迴圈退出,才能繼續執行後面的任務,例如,當彈出MessageBox對話方塊時,意味著進入了一個新的訊息迴圈,直到MessageBox的OK或者Cancel按鈕按下之後這個訊息迴圈才能退出。
RunLoop是Chromium在後期代碼重構時引入的類,主要是為了開發人員更加方便地使用嵌套的訊息迴圈,每個RunLoop都有一個run_depth值,表示嵌套的層數,只有當run_depth值大於1時才說明這個RunLoop被嵌套了。RunLoop是個比較特殊的對象,它在棧上建立,當MessageLoop::Run函數執行完畢,RunLoop自動釋放掉:
void MessageLoop::Run() { RunLoop run_loop; run_loop.Run();}
每個MessageLoop中都有一個指標,指向當前正在啟動並執行RunLoop執行個體,調用RunLoop::Run方法時,MessageLoop::current()訊息迴圈中的RunLoop指標也會隨之修改為當前啟動並執行RunLoop,換句話說,如果此時調用MessageLoop::current()->PostTask,那麼將在嵌套的訊息迴圈中執行非同步任務。
嵌套訊息迴圈基本使用方法如下代碼所示:
void RunNestedLoop(RunLoop* run_loop) { MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); run_loop->Run(); }}// 在棧上建立一個新的RunLoopRunLoop nested_run_loop; // 在當前訊息迴圈中非同步啟動一個嵌套的訊息迴圈; MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&RunNestedLoop,Unretained(&nested_run_loop)));// 一旦RunNestedLoop執行,MessageLoop::current()內部的RunLoop指標會指向nested_run_loop, PostTask將在嵌套RunLoop上執行Quit操作 MessageLoop::current()->PostTask(FROM_HERE, nested_run_loop.QuitClosure());// 非同步執行task_callback時,嵌套RunLoop此時已經退出了,恢複到原先的訊息佇列中執行;MessageLoop::current()->PostTask(FROM_HERE,task_callback);
Android系統的訊息迴圈機制
Android平台上訊息迴圈機制的基本原理與Chromium系統中非常相似,不同的是,Android訊息迴圈的抽象是在Java層構建的。
Android系統中,android.os.Looper和android.os.Handler是訊息迴圈機制中兩個非常重要的類。
Looper類似於Chromium的MessageLoop(或者是RunLoop)概念,Android系統中每個線程都可以關聯一個Looper,用於處理非同步訊息或者Runable對象等。Android系統線程預設是沒有關聯任何一個Looper,開發人員可以顯式調用Looper.prepare和Looper.loop為線程建立並運行Looper,直到退出Looper。Looper之間的互動則需要通過類Handler完成。
Handler允許開發人員向線程的訊息佇列發送或者處理訊息和Runable對象,它有兩個用途:1)通過handleMessage方法非同步處理自己線程中的訊息佇列;2)通過sendMessage或post方法系列向其他線程的訊息佇列發送訊息。一個線程可以有多個Handler,Looper輪詢訊息佇列時,負責將訊息派發給目標Handler,由目標Handler的handleMessage來處理這個訊息。
Looper和Handler之間的關係如所示(圖片來源於這裡):
Chromium使用Android的訊息迴圈機制
這裡所討論的主要是針對主線程的訊息迴圈,即UI線程,因為IO線程是在native層建立的,並沒有涉及到與UI元素的互動事件,並且Android也是POSIX系統,不用考慮IO線程訊息迴圈的整合問題。
如上所述,Android系統的訊息迴圈是構建在Java層的,Chromium需要解決的問題,如何在主線程上將C++層負責管理和調度非同步任務的MessageLoop整合到Android系統的控制路徑上。答案當然是使用android.os.Handler。
首先來看看Android平台上MessagePumpForUI具體實現。與其他平台不同的是,MessagePumpForUI的實現中,啟動整個訊息迴圈處理邏輯的Run方法居然不做任何事情,取而代之是,新增了一個Start方法在Chromium中啟用Android系統的訊息迴圈,如下代碼所示:
void MessagePumpForUI::Run(Delegate* delegate) { NOTREACHED()<< "UnitTests should rely on MessagePumpForUIStub in test_stub_android.h";}void MessagePumpForUI::Start(Delegate* delegate) { run_loop_ = newRunLoop(); // Since the RunLoopwas just created above, BeforeRun should be guaranteed to // return true (itonly returns false if the RunLoop has been Quit already). if(!run_loop_->BeforeRun()) NOTREACHED(); JNIEnv* env =base::android::AttachCurrentThread(); system_message_handler_obj_.Reset( Java_SystemMessageHandler_create( env,reinterpret_cast<intptr_t>(delegate)));}
Start方法會通過JNI向Java層請求建立一個繼承於android.os.Handler的SystemMessageHandler,向當前UI線程的Looper增加一個新的Handler類型,它提供了自己的handleMessage方法:
class SystemMessageHandler extends android.os.Handler { private staticfinal int SCHEDULED_WORK = 1; private staticfinal int DELAYED_SCHEDULED_WORK = 2; privateSystemMessageHandler(long messagePumpDelegateNative) { mMessagePumpDelegateNative = messagePumpDelegateNative; } @Override public voidhandleMessage(Message msg) { if (msg.what== DELAYED_SCHEDULED_WORK) { mDelayedScheduledTimeTicks = 0; } nativeDoRunLoopOnce(mMessagePumpDelegateNative,mDelayedScheduledTimeTicks);}…}
那麼,Chromium系統C++層是如何將執行非同步任務的請求發生給AndroidJava層的Handler,以及Handler又是如何去執行在ChromiumC++層的非同步任務呢?
C++層將非同步任務發送給Java層
每當C++層通過調用MessageLoop::current()->PostTask*向UI線程的MessageLoop添加新的非同步任務時,MessageLoop::ScheduleWork都發起調度任務執行的動作,而MessageLoop::ScheduleWork則是請求MessagePump來完成這個動作,其調用鏈為:
所以MessagePumpForUI的ScheduleWork需要將來自C++層請求發送給Java層的SystemMessageHandler:
void MessagePumpForUI::ScheduleWork() { JNIEnv* env =base::android::AttachCurrentThread(); Java_SystemMessageHandler_scheduleWork(env, system_message_handler_obj_.obj());}
相應地,SystemMessageHandler的scheduleWork實現代碼如下:
class SystemMessageHandler extends Handler { ... @CalledByNative private voidscheduleWork() { sendEmptyMessage(SCHEDULED_WORK); } ...}
不難看出,SystemMessageHandler.scheduleWork唯一要做的事情就是向UI線程的訊息佇列發送一個類型SCHEDULED_WORK的訊息。接下來再來看看Java層是如何處理這個類型的訊息的。
Java層Handler處理來自C++層的非同步任務
當UI線程的Looper收到這個SCHEDULED_WORK非同步訊息後,它會準確無誤的派發給SystemMessageHandler,由SystemMessageHandler.handleMessage重載方法去處理這個訊息,如上代碼所示,handleMessage執行了一個定義在MessagePumpForUI中的native方法DoRunLoopOnce,其實現代碼如下:
static void DoRunLoopOnce(JNIEnv* env, jobject obj, jlong native_delegate, jlong delayed_scheduled_time_ticks) { base::MessagePump::Delegate* delegate = reinterpret_cast<base::MessagePump::Delegate*>(native_delegate); bool did_work =delegate->DoWork(); base::TimeTicksnext_delayed_work_time; did_work |=delegate->DoDelayedWork(&next_delayed_work_time); if(!next_delayed_work_time.is_null()) { if(delayed_scheduled_time_ticks == 0 || next_delayed_work_time < base::TimeTicks::FromInternalValue( delayed_scheduled_time_ticks)) { Java_SystemMessageHandler_scheduleDelayedWork(env, obj, next_delayed_work_time.ToInternalValue(), (next_delayed_work_time - base::TimeTicks::Now()).InMillisecondsRoundedUp()); } } if (did_work) return; delegate->DoIdleWork();}
DoRunLoopOnce方法使C++層的MessageLoop有機會去處理自己的非同步任務,包括延時任務和Idle任務。
至此,通過MessagePumpForUI和SystemMessageHandler的實現,Chromium系統在主線程上已經可以無縫整合到Android的訊息迴圈中,
小結
Android SDK提供的Handler類為Chromium系統將自己的訊息迴圈無縫整合到Android系統中提供了相當大的便利性,Chromium的MessageLoop通過JNI向Java層的Handler發送非同步訊息,當Looper派發這個非同步訊息時,Java層的訊息處理器由通過JNI調用native方法請求Chromium的MessageLoop去調用非同步任務的執行,從而完成了Chromium瀏覽器在主線程上與Android系統訊息迴圈的整合工作。
原創文章系列,轉載請註明原始出處為http://blog.csdn.net/hongbomin/article/details/41258469.
Chromium on Android: Android系統上Chromium主訊息迴圈的實現分析