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.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为了实现主消息循环分析的更多相关文章
- Android 4.X 系统加载 so 失败的原因分析
1 so 加载过程 so 加载的过程可以参考小米的系统工程师的文章loadLibrary动态库加载过程分析 2 问题分析 2.1 问题 年前项目里新加了一个 so库,但发现native 方法的找不到的 ...
- Android消息循环分析
我们的经常使用的系统中,程序的工作一般是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的. 消息驱动的原理就是: 1. 有一个消息队列.能够往这个队列中投递消 ...
- Android之MessageQueue、Looper、Handler与消息循环
在android的activity中有各种各样的事件,而这些事件最终是转换为消息来处理的.android中的消息系统涉及到: * 消息发送 * 消息队列 * 消息循环 * 消息分发 * 消息 ...
- Android监听系统短信数据库变化-提取短信内容
由于监听系统短信广播受到权限的限制,所以很多手机可能使用这种方式没法监听广播,从而没办法获取到系统短信,所以又重新开辟一条路. Android监听系统短信数据库内容变化使用场景: 1.监听短信数据库的 ...
- 让Android程序获得系统的权限,实现关机重启,静默安装等功能
引用:http://www.cnblogs.com/welenwho/archive/2012/05/10/2494984.html android想要获得系统权限有几种途径,一种就是你的程序固化的系 ...
- Android manifest之系统自带的permission
Android manifest之系统自带的permission 本文描述Android系统自带的permission.点击查看:“关于permission的原始定义和说明”.点击查看:“Androi ...
- Android应用与系统安全防御
来源:HTTP://WWW.CNBLOGS.COM/GOODHACKER/P/3864680.HTML ANDROID应用安全防御 Android应用的安全隐患包括三个方面:代码安全.数据安全和组件安 ...
- 图解Android - Android GUI 系统 (1) - 概论
Android的GUI系统是Android最重要也最复杂的系统之一.它包括以下部分: 窗口和图形系统 - Window and View Manager System. 显示合成系统 - Surfac ...
- 图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)
Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...
随机推荐
- Gradle 多渠道打包的使用和错误分析(转)
刚接触到android的开发,对什么都陌生的,本文是自己在项目中使用的技术要点总结,大咖遇到可直接飘过..... 1.Gradle 打包(不废话了直接来脚本),将下列脚本放到build.gradle文 ...
- 【Cocos2d-x】源代码分析之 2d/ui/Widget
从今天開始 咱也模仿 红孩儿这些大牛分析源代码 ,因为水平有限 不正确之处欢迎狂喷.哈哈. #ifndef __UIWIDGET_H__ #define __UIWIDGET_H__ #include ...
- 【原创】leetCodeOj --- Jump Game II 解题报告
原题地址: https://oj.leetcode.com/problems/jump-game-ii/ 题目内容: Given an array of non-negative integers, ...
- vc++笔记十一
一.LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 连接器LNK是通过调用cvtres.exe完毕文件向coff格式的转换的,所以出现这样的错误的原因就是cvtres.exe出现了问题 ...
- 程序员联盟有自己的论坛啦!基于Discuz构建,还不来注册~
我把程序员联盟网站的论坛建好了,哈哈哈.用的是Discuz这个腾讯旗下的中文bbs建设软件.正在完善论坛,添加各种模块和应用.大家可以先去注册一下:coderunity.com/bbs/forum.p ...
- MSSQL连接字符串,你真的清楚吗?
原文:MSSQL连接字符串,你真的清楚吗? 几年前当我第一次面试时,考官发现我是个新手于是他让我写个连接字符串,虽然当时就知道X种连接字符串的写法,但是当时却没能写对一个,工作多年后我仍然不能写一个正 ...
- skyeye安装+arm-elf-gdb安装+模拟s3c44b0x+执行ucos4skyeye
[假设你要引用.请阅读所有,这里是我的为期两天的过程只是一个记录] skyeye安装:ubuntu12.0432 llvm2.8 skyeye1.3.3 http://blog.chinaunix.n ...
- 学习pthreads,创建和终止多线程
更CPU多线程编程,通过笔者的研究发现,,pthreads使用日趋广泛.它是螺纹POSIX标准,它定义了一组线程的创建和操作API. 配置环境见上博客文章.配置环境后,只需要加入#include &l ...
- can't connect to mysql server on localhost <10061>
需要启动MySQL服务.它可以通过两种方式来启动使用MySQL: 1.命令行模式. Win+R,进入cmd然后按Enter键.在命令行形式的输入: net start mysql56 mysql56是 ...
- 怎么将Emeditor设置成网页查看源代码的默认编译器
1.打开emditor: 2.在菜单栏中找到工具---->自定义,打开自定义窗口: 3.快捷方式--->更多快捷方式 5.选中“在internet explorer中通过emeditor查 ...