Chromium on Android: Android在系統Chromium為了實現主消息循環分析


總結:剛開始接觸一個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類型差點兒相同,但創建時不能使用主線程上的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或者Cancelbutton按下之后這個消息循環才干退出。

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.Looperandroid.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.


版權聲明:本文博客原創文章。博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM