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或者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<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.
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。