总结:刚开始接触一个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.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.
版权声明:本文博客原创文章。博客,未经同意,不得转载。