摘要:剛一開始接觸Chromium on Android時,就很好奇Chromium的主消息循環是怎麼整合到Android應用程序中的。對於Android程序來說,一旦啟動,主線程就會有一個Java層的消息循環處理用戶輸入事件等系統事件,而對Chromium來說,它有自己另一套消息循環的實現,這個實現有哪些特點,又將如何無縫整合到Android Java層的消息循環中去,正是本文所要討論的話題。
消息循環和主消息循環
消息循環,或叫做事件循環,是異步編程模型中一個重要的概念,用來處理單個線程中發生的異步事件,這些異步事件包括用戶輸入事件,系統事件,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類型差不多,但創建時不能使用主線程上的MessagePump工廠方法。註:該類型的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(); } } // 在棧上創建一個新的RunLoop RunLoop 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(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(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系統消息循環的整合工作。
原創文章系列,轉載請註明原始出處為https://blog.csdn.net/hongbomin/article/details/41258469.