书名:深入理解Android:卷I
作者:邓凡平
ISBN:9787111357629
定价:69.00元
出版社:机械工业出版社华章公司
China-pub:http://product.china-pub.com/198566
内容简介:
全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和源码阅读方法的介绍;第2章通过对Android系统中的MediaScanner进行分析,详细讲解了Android中十分重要的JNI技术;第3章分析了init进程,揭示了通过解析init.rc来启动Zygote以及属性服务的工作原理;第4章分析了Zygote、SystemServer等进程的工作机制,同时还讨论了Android的启动速度、虚拟机HeapSize的大小调整、Watchdog工作原理等问题;第5章讲解了Android系统中常用的类,包括sp、wp、RefBase、Thread等类,同步类,以及Java中的Handler类和Looper类,掌握这些类的知识后方能在后续的代码分析中做到游刃有余;第6章以MediaServer为切入点,对Android中极为重要的Binder进行了较为全面的分析,深刻揭示了其本质。第7章对Audio系统进行了深入的分析,尤其是AudioTrack、AudioFlinger和AudioPolicyService等的工作原理。第8章深入讲解了Surface系统的实现原理,分析了Surface与Activity之间以及Surface与SurfaceFlinger之间的关系、SurfaceFlinger的工作原理、Surface系统中的帧数据传输以及LayerBuffer的工作流程。第9章对Vold和Rild的原理和机制进行了深入的分析,同时还探讨了Phone设计优化的问题;第10章分析了多媒体系统中MediaScanner的工作原理。本书适合有一定基础的Android应用开发工程师和系统工程师阅读。通过对本书的学习,大家将能更深刻地理解Android系统,从而自如应对实际开发中遇到的难题。
作者简介:
邓凡平,资深Android开发工程师,热衷于Android源代码的研究,对Android的架构设计和实现原理有非常深刻的认识和理解,应用开发经验也十分丰富。目前就职于国内一家领先的Android企业,负责Framework的开发和维护。乐于分享,活跃于CSDN等专业社区,撰写的Android Framework源码的系列文章深受读者欢迎。此外,他对Linux内核、C/C++/Python相关的技术,以及高性能网络服务器和多核并行开发等也有一定的研究。博客地址:http://blog.csdn.net/Innost 。
《深入理解Android:卷I》推荐序
近两年来,IT行业的最热点聚焦到了移动互联网上。PC时代,WINTEL联盟成就了英特尔和微软各自的霸业。移动互联网时代,谁将上演新的传奇?新生的Android当年仅用短短一年多的时间就跻身全球智能操作系统的三甲行列。在北美市场,如今Android已经超过iOS和黑莓系统成为老大!Android势不可挡,ARM+Android组合的前景一片光明,越来越多的从业者加入了Android行列!
与带给人们良好用户体验的iOS不一样的是,Android是一个开放的系统,其所有代码都是开源的。因此,对于开发者而言,不仅可以做到知其然,更可以做到知其所以然!
然而,要想知道其所以然,并不是一件简单的事情。回想当初,我开始接触Android的时候,除了Android源码外,其他资料甚少。Android是基于Linux的完整操作系统,其代码量让人望而生畏。可以想象,在没有指导的情况下一头扎进操作系统庞大的代码中是一件让人多么痛苦的事情。时间过得很快,Android生态链已经得到了充分的发展。现在市场上的Android资料已经开始泛滥,书籍已经数不胜数。然而,绝大部分书籍只限于讲解Android应用的开发(拜Android应用API所赐),没有深入到系统级的探讨,极少的所谓提供Android深入指导的资料也只是浅尝辄止。如果想深入了解Android系统,只有华山一条路:自己看Android源代码!
正是因为如此,当初凡平告诉我他要系统地整理其深入钻研Android源代码的心得时,我表示了强烈的赞同。这是一件极少有人做过的事情,这件事情将给已经或即将跨入Android世界的同仁们极大的帮助!这本书里,作者以代码框架为主线,用循序渐进的方式将框架中的关键点一一剖开,从而给读者一个架构清楚、细节完善的立体展现。另外,凡平还会用他的幽默给正在啃枯燥代码的您带来不少笑意和轻松。毫无疑问,如果您想深入了解Android系统,这本书就是您进入Android神秘世界的钥匙。
如果您看准了移动互联网的前景,想深入理解Android,那就让这本书指导您前进吧!
邓必山
2011年6月于北京
《深入理解Android:卷I》前言
虽然前言位于书的最前面,但往往是最后才完成的。至今,本书的撰写工作算是基本完成了,在书稿付梓之前,心中却有些许忐忑和不安,因为拙著可能会存在Bug。为此,我先为书中可能存在的Bug将给大家带来的麻烦致以真诚的歉意。另外,如果大家发现本书存在纰漏或有必要进一步探讨的地方,请发邮件给我,我会尽快回复。非常乐意与大家交流。
本书主要内容
全书一共10章,其中一些重要章节中还设置了“拓展思考”部分。这10章的主要内容是:
第1章介绍了阅读本书所需要做的一些准备工作,包括对Android整个系统架构的认识,以及Android开发环境和源码阅读环境的搭建等。注意,本书分析的源码是Android2.2。
第2章通过Android源码中的一处实例深入地介绍了JNI技术。
第3章围绕init进程,介绍了如何解析init.rc以启动Zygote和属性服务(property service)的工作原理。
第4章剖析了zygote和system_server进程的工作原理。本章的拓展思考部分讨论了Andorid的启动速度、虚拟机heapsize的大小调整问题以及“看门狗”的工作原理。
第5章讲解了Android源码中常用的类,如sp、wp、RefBase、Thread类、同步类、Java中的Handler类以及Looper类。这些类都是Android中最常用和最基本的,只有掌握这些类的知识,才能在分析后续的代码时游刃有余。
第6章以MediaServer为切入点,对Binder进行了较为全面的分析。本章拓展思考部分讨论了与Binder有关的三个问题,它们分别是Binder和线程的关系、死亡通知以及匿名Service。笔者希望,通过本章的学习,大家能更深入地认识Binder的本质。
第7章阐述了Audio系统中的三位重要成员AudioTrack、AudioFlinger和AudioPolicyService的工作原理。本章拓展思考部分分析了AudioFlinger中DuplicatingThread的工作原理,并且和读者一道探讨了单元测试、ALSA、Desktop check等问题。通过对本章的学习,相信读者会对Audio系统有更深的理解。
第8章以Surface系统为主,分析了Activity和Surface的关系、Surface和SurfaceFlinger的关系以及SurfaceFlinger的工作原理。本章的拓展思考部分分析了Surface系统中数据传输控制对象的工作原理、有关ViewRoot的一些疑问,最后讲解了LayerBuffer的工作流程。这是全书中难度较大的一章,建议大家反复阅读和思考,这样才能进一步深入理解Surface系统。
第9章分析了Vold和Rild,其中Vold负责Android平台中外部存储设备的管理,而Rild负责与射频通信有关的工作。本章的拓展思考部分介绍了嵌入式系统中与存储有关的知识,还探讨了Rild和Phone设计优化方面的问题。
第10章分析了多媒体系统中MediaScanner的工作原理。在本章的拓展思考部分,笔者提出了几个问题,旨在激发读者深入思考和学习Android的欲望。
本书特色
笔者认为,本书最大的特点在于,较全面、系统、深入地讲解了Android系统中的几大重要组成部分的工作原理,旨在通过直接剖析源代码的方式,引领读者一步步深入于诸如Binder、Zygote、Audio、Surface、Vold、Rild等模块的内部,去理解它们是如何实现的,以及如何工作的。笔者根据研究Android代码的心得,在本书中尝试性地采用了精简流程、逐个击破的方法进行讲解,希望这样做能帮助读者更快、更准确地把握各模块的工作流程及其本质。本书大部分章节中都专门撰写了“拓展思路”的内容,希望这部分内容能激发读者对Android代码进行深入研究的热情。
本书面向的读者
(1)Android应用开发工程师
对于Android应用开发工程师而言,本书中关于Binder,以及sp、wp、Handler和Looper等常用类的分析或许能帮助你迅速适应Android平台上的开发工作。
(2)Android系统开发工程师
Android系统开发工程师常常需要深入理解系统的运转过程,而本书所涉及的内容可能正是他们在工作和学习中最想了解的。那些对具体模块(如Audio系统和Surface系统)感兴趣的读者也可以直接阅读相关章节的内容。
这里有必要提醒一下,要阅读此书,应具有C++的基本知识,因为本书的大部分内容都集中在了Native层。
如何阅读本书
本书是在分析Android源码的基础上展开的,而源码文件所在的路径一般都很长,例如,文件AndroidRuntime.cpp的真实路径就是framework/base/core/jni/AndroidRuntime.cpp。为了书写方便起见,我们在各章节开头把该章所涉及的源码路径全部都列出来了,而在具体分析源码时,则只列出该源码的文件名。
下面就是一个示例:
[-->AndroidRuntime.cpp]
//这里是源码分析和一些注释。
如有一些需要特别说明的地方,则会用下面的格式表示:
[-->AndroidRuntime.cpp::特别说明]
特别说明可帮助读者找到源码中的对应位置。
另外,本书在描述类之间的关系以及在函数调用流程上使用了UML的静态类图以及序列图。UML是一个强大的工具,但它的建模规范过于烦琐,为更简单清晰地描述事情的本质,本书并未完全遵循UML的建模规范。这里仅举一例,如图1所示:
图1 UML示例图
如上图所示:
画在外部类内部的方框用于表示内部类。
接口和普通类用同一种框图表示。
本书所使用的UML图都比较简单,读者大可不必花费时间专门学习UML。
本书的编写顺序,其实应该是6、5、4、7、8、9、10、2、3、1章,但出于逻辑连贯性的考虑,还是建议读者按本书的顺序阅读。其中,第2、5、6章分别讲述了JNI、Android常用类以及Binder系统,这些都是基础知识,我们有必要完全掌握。其他部分的内容都是针对单个模块的,例如Zygote、Audio、Surface、MediaScanner等,读者可各取所需,分别对其进行研究。
致谢
首先要感谢杨福川编辑。本书最初的内容来自我的博客http://blog.csdn.net/Innost
,但博客里的文章都没有图,格式也较混乱。是杨编辑最先鼓励我将这些博文整理修改成册,所以我对杨福川编辑的眼光佩服得五体投地。在他的同事杨绣国和白宇的帮助下,我最终才将博客中那些杂乱的文章撰成了今天这本图文并茂、格式工整的书籍。
其次要感谢我的妻子。为写成此书,我几乎将周末所有的时间都花在了工作中,而长时间在生活上对妻子不闻不问。对丈夫呆若木鸡式的冷淡,妻子却给予了最大的宽容。另外,我的岳父母和我的父母亲都给予了我最无私的帮助,他们都是平凡而伟大的父母亲。还有我和妻子的亲戚们,他们的宽厚和善良时刻感动着我。
在IT职业的道路上,非常感念前东家中科大洋公司的领导和同事们,他们是邓伟先生、刘运红先生、王宁先生等。当初,如果没有他们宽容的接纳和细心的指导,现在我不可能成为一名合格的程序员。
非常感谢我现在供职的单位中科创达公司。在这里工作,我常有这样一种感慨:不是所有人都能自己开公司创业的,而又有多少人能够有机会和一个优秀的创业公司一起成长、一起发展呢?创达开明的领导、睿智而富有激情的工作伙伴正是孕育本书的沃土。公司领导赵鸿飞先生、吴安华女士等人更是给予了我最大的肯定和鼓励。
这里要特别提及的是,我的大学同窗,即为本书作序的邓必山先生。如果没有他的推荐,凭自己那份简陋、单薄的简历,是根本无法与Android亲密接触的。另外,他还曾在技术和个人发展上给予过我很多的指导,对此,我将永志不忘!
谢谢那些共享Android知识的网友们!没有大家前期点滴的奉献,或许我至今还在琢磨着某段代码呢。
最后应感谢的是肯花费时间和精力阅读本书的读者,你们的意见和建议将会是我获得的巨大的精神财富!
邓凡平
2011年6月于北京
《深入理解Android:卷I》目录
1.1 系统架构 / 2
1.1.1 Android系统架构 / 2
1.1.2 本书的架构 / 3
1.2 搭建开发环境 / 4
1.3 工具介绍 / 8
1.4 本章小结 / 12
第2章 深入理解JNI / 13
2.1 JNI概述 / 14
2.2 学习JNI的实例:MediaScanner / 15
2.3 Java层的MediaScanner分析 / 16
2.4 JNI层MediaScanner的分析 / 17
2.5 本章小结 / 32
第3章 深入理解init / 33
3.1 概述 / 34
3.2 init分析 / 34
3.3 本章小结 / 60
第4章 深入理解zygote / 61
4.1 概述 / 62
4.2 zygote分析 / 62
4.3 SystemServer分析 / 74
4.4 zygote的分裂 / 84
4.5 拓展思考 / 88
4.6 本章小结 / 93
第5章 深入理解常见类 / 95
5.1 概述 / 96
5.2 以“三板斧”揭秘RefBase、sp和wp / 96
5.3 Thread类及常用同步类分析 / 109
5.4 Looper和Handler类分析 / 121
5.5 本章小结 / 129
第6章 深入理解Binder / 130
6.1 概述 / 131
6.2 庖丁解MediaServer / 132
6.3 服务总管ServiceManager / 152
6.4 MediaPlayerService和它的Client / 158
6.5 拓展思考 / 162
6.6 学以致用 / 166
6.7 本章小结 / 172
第7章 深入理解Audio系统 / 173
7.1 概述 / 174
7.2 AudioTrack的破解 / 174
7.3 AudioFlinger的破解 / 200
7.4 AudioPolicyService的破解 / 234
7.5 拓展思考 / 262
7.6 本章小结 / 272
第8章 深入理解Surface系统 / 273
8.1 概述 / 275
8.2 一个Activity的显示 / 275
8.3 初识Surface / 297
8.4 深入分析Surface / 310
8.5 SurfaceFlinger分析 / 353
8.6 拓展思考 / 377
8.7 本章小结 / 394
第9章 深入理解Vold和Rild / 395
9.1 概述 / 396
9.2 Vold的原理与机制分析 / 396
9.3 Rild的原理与机制分析 / 428
9.4 拓展思考 / 459
9.5 本章小结 / 463
第10章 深入理解MediaScanner / 464
10.1 概述 / 465
10.2 android.process.media分析 / 465
10.3 MediaScanner分析 / 472
10.4 拓展思考 / 486
10.5 本章小结 / 488
第5章 深入理解常见类
本章涉及的源代码文件名称及位置
下面是本章分析的源码文件名和它的位置。
RefBase.h(framework/base/include/utils/RefBase.h)
RefBase.cpp(framework/base/libs/utils/RefBase.cpp)
Thread.cpp(framework/base/libs/utils/Thread.cpp)
Thread.h(framework/base/include/utils/Thread.h)
Atomic.h(system/core/include/cutils/Atomic.h)
AndroidRuntime.cpp(framework/base/core/jni/AndroidRuntime.cpp)
Looper.java(framework/base/core/Java/Android/os/Looper.java)
Handler.java(framework/base/core/Java/Android/os/Handler.java)
HandlerThread.java(framework/base/core/Java/Android/os/HandlerThread.java)
5.1 概述
5.2 以“三板斧”揭秘RefBase、sp和wp
RefBase是Android中所有对象的始祖,类似于MFC中的CObject及Java中的Object对象。在Android中,RefBase结合sp和wp,实现了一套通过引用计数的方法来控制对象生命周期的机制。就如我们想像的那样,这三者的关系非常暧昧。初次接触Android源码的人往往会被那个随处可见的sp和wp搞晕了头。
什么是sp和wp呢?其实,sp并不是我开始所想的smart pointer(C++语言中有这个东西),它真实的意思应该是strong pointer,而wp则是weak pointer的意思。我认为,Android推出这一套机制可能是模仿Java,因为Java世界中有所谓weak reference之类的东西。sp和wp的目的,就是为了帮助健忘的程序员回收new出来的内存。
这三者的关系比较复杂,都说程咬金的“三板斧”很厉害,那么我们就借用这三板斧,揭密其间的暧昧关系。
5.2.1 第一板斧——初识影子对象
我们的“三板斧”,其实就是三个例子。相信这三板斧劈下去,你会很容易理解它们。
[-->例子1]
//类A从RefBase派生,RefBase是万物的始祖。 class A:public RefBase { //A没有任何自己的功能。 } int main() { A* pA = new A; { //注意我们的sp、wp对象是在{}中创建的,下面的代码先创建sp,然后创建wp。 sp<A> spA(pA); wp<A> wpA(spA); //大括号结束前,先析构wp,再析构sp。 } }
例子够简单吧?但也需一步一步分析这斧子是怎么劈下去的。
1. RefBase和它的影子
类A从RefBase中派生。使用的是RefBase构造函数。代码如下所示:
[-->RefBase.cpp]
RefBase::RefBase() : mRefs(new weakref_impl(this))//注意这句话 { //mRefs是RefBase的成员变量,类型是weakref_impl,我们暂且叫它影子对象。 //所以A有一个影子对象。 }
mRefs是引用计数管理的关键类,需要进一步观察。它是从RefBase的内部类weakref_type中派生出来的。
先看看它的声明:
class RefBase::weakref_impl : public RefBase::weakref_type //从RefBase的内部类weakref_type派生。
由于Android频繁使用C++内部类的方法,所以初次阅读Android代码时可能会有点不太习惯,C++的内部类和Java的内部类相似,但有一点不同,即它需要一个显式的成员指向外部类对象,而Java的内部类对象有一个隐式的成员指向外部类对象的。
[-->RefBase.cpp::weakref_imple构造]
weakref_impl(RefBase* base) : mStrong(INITIAL_STRONG_VALUE) //强引用计数,初始值为0x1000000。 , mWeak(0) //弱引用计数,初始值为0。 , mBase(base)//该影子对象所指向的实际对象。 , mFlags(0) , mStrongRefs(NULL) , mWeakRefs(NULL) , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT) , mRetain(false) { }
如你所见,new了一个A对象后,其实还new了一个weakref_impl对象,这里称它为影子对象,另外我们称A为实际对象。
这里有一个问题:影子对象有什么用?
可以仔细想一下,是不是发现影子对象成员中有两个引用计数?一个强引用,一个弱引用。如果知道引用计数和对象生死有些许关联的话,就容易想到影子对象的作用了。
2.sp上场
程序继续运行,现在到了:
sp<A> spA(pA);
请看sp的构造函数,它的代码如下所示(注意,sp是一个模板类,对此不熟悉的读者可以去翻翻书,或者干脆把所有出现的T都换成A):
[-->RefBase.h::sp(T* other)]
template<typename T> sp<T>::sp(T* other) //这里的other就是刚才创建的pA。 : m_ptr(other)// sp保存了pA的指针。 { if (other) other->incStrong(this);//调用pA的incStrong。 }
OK,战场转到RefBase的incStrong中。它的代码如下所示:
[-->RefBase.cpp]
void RefBase::incStrong(const void* id) const { //mRefs就是刚才在RefBase构造函数中new出来的影子对象。 weakref_impl* const refs = mRefs; //操作影子对象,先增加弱引用计数。 refs->addWeakRef(id); refs->incWeak(id); ......
先来看看影子对象的这两个weak函数都干了些什么。
(1)眼见而心不烦
下面看看第一个函数addWeakRef,代码如下所示:
[-->RefBase.cpp]
void addWeakRef(const void* /*id*/) { }
呵呵,addWeakRef啥都没做,因为这是release版走的分支。调试版的代码我们就不讨论了,它是给创造RefBase、 sp,以及wp的人调试用的。
总之,一共有这么几个不用考虑的函数,下面都已列出来了。以后再碰见它们,干脆就直接跳过去:
void addStrongRef(const void* /*id*/) { } void removeStrongRef(const void* /*id*/) { } void addWeakRef(const void* /*id*/) { } void removeWeakRef(const void* /*id*/) { } void printRefs() const { } void trackMe(bool, bool) { }
继续我们的征程。再看incWeak函数,代码如下所示:
[-->RefBase.cpp]
void RefBase::weakref_type::incWeak(const void* id) { weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->addWeakRef(id); //上面说了,非调试版什么都不干。 const int32_t c = android_atomic_inc(&impl->mWeak); //原子操作,影子对象的弱引用计数加1。 //千万记住影子对象的强弱引用计数的值,这是彻底理解sp和wp的关键。 }
好,我们再回到incStrong,继续看代码:
[-->RefBase.cpp]
...... //刚才增加了弱引用计数。 //再增加强引用计数。 refs->addStrongRef(id); //非调试版这里什么都不干。 //下面函数为原子加1操作,并返回旧值。所以c=0x1000000,而mStrong变为0x1000001。 const int32_t c = android_atomic_inc(&refs->mStrong); if (c != INITIAL_STRONG_VALUE) { //如果c不是初始值,则表明这个对象已经被强引用过一次了。 return; } //下面这个是原子加操作,相当于执行refs->mStrong +(-0x1000000),最终mStrong=1。 android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); /* 如果是第一次引用,则调用onFirstRef,这个函数很重要,派生类可以重载这个函数,完成一些 初始化工作。 */ const_cast<RefBase*>(this)->onFirstRef(); }
(2)sp构造的影响
sp构造完后,它给这个世界带来了什么?
那就是在RefBase中影子对象的强引用计数变为1,且弱引用计数也变为1。
更准确的说法是,sp的出生导致影子对象的强引用计数加1,且弱引用计数也加1。
(3)wp构造的影响
继续看wp,例子中的调用方式如下:
wp<A> wpA(spA)
[-->RefBase.h::wp(const sp<T>& other)]
template<typename T> wp<T>::wp(const sp<T>& other) : m_ptr(other.m_ptr) //wp的成员变量m_ptr指向实际对象。 { if (m_ptr) { //调用pA的createWeak,并且保存返回值到成员变量m_refs中。 m_refs = m_ptr->createWeak(this); } }
[-->RefBase.cpp]
RefBase::weakref_type* RefBase::createWeak(const void* id) const { //调用影子对象的incWeak,这个我们刚才讲过了,它会导致影子对象的弱引用计数增加1。 mRefs->incWeak(id); return mRefs; //返回影子对象本身。 }
我们可以看到,wp化后,影子对象的弱引用计数将增加1,所以现在弱引用计数为2,而强引用计数仍为1。另外,wp中有两个成员变量,一个保存实际对象,另一个保存影子对象。sp只有一个成员变量,用来保存实际对象,但这个实际对象内部已包含了对应的影子对象。
OK,wp创建完了,现在开始进行wp的析构。
(4)wp析构的影响
wp进入析构函数,则表明它快要离世了,代码如下所示:
[-->RefBase.h]
template<typename T> wp<T>::~wp() { if (m_ptr) m_refs->decWeak(this); //调用影子对象的decWeak,由影子对象的基类实现。 }
[-->RefBase.cpp]
void RefBase::weakref_type::decWeak(const void* id) { //把基类指针转换成子类(影子对象)的类型,这种做法有些违背面向对象编程的思想。 weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id);//非调试版不做任何事情。 //原子减1,返回旧值,c=2,而弱引用计数从2变为1。 const int32_t c = android_atomic_dec(&impl->mWeak); if (c != 1) return; //c=2,直接返回。 //如果c为1,则弱引用计数为0,这说明没用弱引用指向实际对象,需要考虑是否释放内存。 // OBJECT_LIFETIME_XXX和生命周期有关系,我们后面再说。 if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { if (impl->mStrong == INITIAL_STRONG_VALUE) delete impl->mBase; else { delete impl; } } else { impl->mBase->onLastWeakRef(id); if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { delete impl->mBase; } } }
在例1中,wp析构后,弱引用计数减1。但由于此时强引用计数和弱引用计数仍为1,所以没有对象被干掉,即没有释放实际对象和影子对象占据的内存。
(5)sp析构的影响
下面进入sp的析构。
[-->RefBase.h]
template<typename T> sp<T>::~sp() { if (m_ptr) m_ptr->decStrong(this); //调用实际对象的decStrong,由RefBase实现。 }
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->removeStrongRef(id);//调用影子对象的removeStrongRef,啥都不干。 //注意,此时强弱引用计数都是1,下面函数调用的结果是c=1,强引用计数为0。 const int32_t c = android_atomic_dec(&refs->mStrong); if (c == 1) { //对于我们的例子, c为1 //调用onLastStrongRef,表明强引用计数减为0,对象有可能被delete。 const_cast<RefBase*>(this)->onLastStrongRef(id); //mFlags为0,所以会通过delete this把自己干掉。 //注意,此时弱引用计数仍为1。 if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { delete this; } ...... }
先看delete this的处理,它会导致A的析构函数被调用。再来看A的析构函数,代码如下所示:
[-->例子1::~A()]
//A的析构直接导致进入RefBase的析构。 RefBase::~RefBase() { if (mRefs->mWeak == 0) { //弱引用计数不为0,而是1。 delete mRefs; } }
RefBase的delete this自杀行为没有把影子对象干掉,但我们还在decStrong中,可从delete this接着往下看:
[-->RefBase.cpp]
.... //接前面的delete this if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { delete this; } //注意,实际数据对象已经被干掉了,所以mRefs也没有用了,但是decStrong刚进来 //的时候就把mRefs保存到refs了,所以这里的refs指向影子对象。 refs->removeWeakRef(id); refs->decWeak(id);//调用影子对象decWeak }
[-->RefBase.cpp]
void RefBase::weakref_type::decWeak(const void* id) { weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id);//非调试版不做任何事情。 //调用前影子对象的弱引用计数为1,强引用计数为0,调用结束后c=1,弱引用计数为0。 const int32_t c = android_atomic_dec(&impl->mWeak); if (c != 1) return; //这次弱引用计数终于变为0了,并且mFlags为0, mStrong也为0。 if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { if (impl->mStrong == INITIAL_STRONG_VALUE) delete impl->mBase; else { delete impl; //impl就是this,把影子对象也就是自己干掉。 } } else { impl->mBase->onLastWeakRef(id); if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { delete impl->mBase; } } }
好,第一板斧劈下去了!来看看它的结果是什么。
3.第一板斧的结果
第一板斧过后,来总结一下刚才所学的知识:
- RefBase中有一个隐含的影子对象,该影子对象内部有强弱引用计数。
- sp化后,强弱引用计数各增加1,sp析构后,强弱引用计数各减1。
- wp化后,弱引用计数增加1,wp析构后,弱引用计数减1。
完全彻底地消灭RefBase对象,包括让实际对象和影子对象灭亡,这些都是由强弱引用计数控制的,另外还要考虑flag的取值情况。当flag为0时,可得出如下结论:
- 强引用为0将导致实际对象被delete。
- 弱引用为0将导致影子对象被delete。
5.2.2 第二板斧——由弱生强
再看第二个例子,代码如下所示:
[-->例子2]
int main() { A *pA = new A(); wp<A> wpA(pA); sp<A> spA = wpA.promote();//通过promote函数,得到一个sp。 }
对A的wp化,不再做分析了。按照前面所讲的知识,wp化后仅会使弱引用计数加1,所以此处wp化的结果是:
影子对象的弱引用计数为1,强引用计数仍然是初始值0x1000000。
wpA的promote函数是从一个弱对象产生一个强对象的重要函数,试看——
1. 由弱生强的方法
代码如下所示:
[-->RefBase.h]
template<typename T> sp<T> wp<T>::promote() const { return sp<T>(m_ptr, m_refs); //调用sp的构造函数。 } [-->RefBase.h] template<typename T> sp<T>::sp(T* p, weakref_type* refs) : m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)//有点看不清楚。 { //上面那行代码够简洁,但是不方便阅读,我们写成下面这样: /* T* pTemp = NULL; //关键函数attemptIncStrong if(p != NULL && refs->attemptIncStrong(this) == true) pTemp = p; m_ptr = pTemp; */ }
2.成败在此一举
由弱生强的关键函数是attemptIncStrong,它的代码如下所示:
[-->RefBase.cpp]
bool RefBase::weakref_type::attemptIncStrong(const void* id) { incWeak(id); //增加弱引用计数,此时弱引用计数变为2。 weakref_impl* const impl = static_cast<weakref_impl*>(this); int32_t curCount = impl->mStrong; //这个仍是初始值。 //下面这个循环,在多线程操作同一个对象时可能会循环多次。这里可以不去管它, //它的目的就是使强引用计数增加1。 while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) { if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) { break; } curCount = impl->mStrong; } if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) { bool allow; /* 下面这个allow的判断极为精妙。impl的mBase对象就是实际对象,有可能已经被delete了。 curCount为0,表示强引用计数肯定经历了INITIAL_STRONG_VALUE->1->...->0的过程。 mFlags就是根据标志来决定是否继续进行||或&&后的判断,因为这些判断都使用了mBase, 如不做这些判断,一旦mBase指向已经回收的地址,你就等着segment fault吧! 其实,咱们大可不必理会这些东西,因为它不影响我们的分析和理解。 */ if (curCount == INITIAL_STRONG_VALUE) { allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); } else { allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); } if (!allow) { //allow为false,表示不允许由弱生强,弱引用计数要减去1,这是因为咱们进来时加过一次。 decWeak(id); return false; //由弱生强失败。 } //允许由弱生强,强引用计数要增加1,而弱引用计数已经增加过了。 curCount = android_atomic_inc(&impl->mStrong); if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) { impl->mBase->onLastStrongRef(id); } } impl->addWeakRef(id); impl->addStrongRef(id);//两个函数调用没有作用。 if (curCount == INITIAL_STRONG_VALUE) { //强引用计数变为1。 android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong); //调用onFirstRef,通知该对象第一次被强引用。 impl->mBase->onFirstRef(); } return true; //由弱生强成功。 }
3. 第二板斧的结果
promote完成后,相当于增加了一个强引用。根据上面所学的知识可知:
由弱生强成功后,强弱引用计数均增加1。所以现在影子对象的强引用计数为1,弱引用计数为2。
5.2.3 第三板斧——破解生死魔咒
1. 延长生命的魔咒
RefBase为我们提供了一个这样的函数:
extendObjectLifetime(int32_t mode) 另外还定义了一个枚举: enum { OBJECT_LIFETIME_WEAK = 0x0001, OBJECT_LIFETIME_FOREVER = 0x0003 };
注意:FOREVER的值是3,用二进制表示是B11,而WEAK的二进制是B01,也就是说FOREVER包括了WEAK的情况。
上面这两个枚举值,是破除强弱引用计数作用的魔咒。先观察flags为OBJECT_LIFETIME_ WEAK的情况,见下面的例子。
[-->例子3]
class A:public RefBase { public A() { extendObjectLifetime(OBJECT_LIFETIME_WEAK);//在构造函数中调用。 } } int main() { A *pA = new A(); wp<A> wpA(pA);//弱引用计数加1。 { sp<A> spA(pA) //sp后,结果是强引用计数为1,弱引用计数为2。 } .... }
sp的析构将直接调用RefBase的decStrong,它的代码如下所示:
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->removeStrongRef(id); const int32_t c = android_atomic_dec(&refs->mStrong); if (c == 1) { //上面进行原子操作后,强引用计数为0 const_cast<RefBase*>(this)->onLastStrongRef(id); //注意这句话。如果flags不是WEAK或FOREVER的话,将delete 数据对象。 //现在我们的flags是WEAK,所以不会delete 它。 if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { delete this; } } refs->removeWeakRef(id); refs->decWeak(id);//调用前弱引用计数是2。 }
然后调用影子对象的decWeak。再来看它的处理,代码如下所示:
[-->RefBase.cpp::weakref_type的decWeak()函数]
void RefBase::weakref_type::decWeak(const void* id) { weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id); const int32_t c = android_atomic_dec(&impl->mWeak); if (c != 1) return; //c为2,弱引用计数为1,直接返回。 /* 假设我们现在到了例子中的wp析构之处,这时也会调用decWeak,在调用上面的原子减操作后 c=1,弱引用计数变为0,此时会继续往下运行。由于mFlags为WEAK ,所以不满足if的条件。 */ if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { if (impl->mStrong == INITIAL_STRONG_VALUE) delete impl->mBase; else { delete impl; } } else {//flag为WEAK,满足else分支的条件。 impl->mBase->onLastWeakRef(id); /* 由于 flags值满足下面这个条件,所以实际对象会被delete,根据前面的分析可知,实际对象的delete会检查影子对象的弱引用计数,如果它为0,则会把影子对象也delete掉。 由于影子对象的弱引用计数此时已经为0,所以影子对象也会被delete。 */ if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { delete impl->mBase; } } }
2. LIFETIME_WEAK的魔力
看完上面的例子,我们发现什么了?
在LIFETIME_WEAK的魔法下,强引用计数为0,而弱引用计数不为0的时候,实际对象没有被delete!只有当强引用计数和弱引用计数同时为0时,实际对象和影子对象才会被delete。
3. 魔咒大揭秘
至于LIFETIME_FOREVER的破解,就不用再来一斧子了,我直接给出答案:
- flags为0,强引用计数控制实际对象的生命周期,弱引用计数控制影子对象的生命周期。强引用计数为0后,实际对象被delete。所以对于这种情况,应记住的是,使用wp时要由弱生强,以免收到segment fault信号。
- flags为LIFETIME_WEAK,强引用计数为0,弱引用计数不为0时,实际对象不会被delete。当弱引用计数减为0时,实际对象和影子对象会同时被delete。这是功德圆满的情况。
- flags为LIFETIME_FOREVER,对象将长生不老,彻底摆脱强弱引用计数的控制。所以你要在适当的时候杀死这些“老妖精”,免得她祸害“人间”。
5.2.4 轻量级的引用计数控制类LightRefBase
上面介绍的RefBase,是一个重量级的引用计数控制类。那么,究竟有没有一个简单些的引用计数控制类呢?Android为我们提供了一个轻量级的LightRefBase。这个类非常简单,我们不妨一起来看看。
[-->RefBase.h]
template <class T> class LightRefBase { public: inline LightRefBase() : mCount(0) { } inline void incStrong(const void* id) const { //LightRefBase只有一个引用计数控制量mCount。incStrong的时候使它增加1。 android_atomic_inc(&mCount); } inline void decStrong(const void* id) const { //decStrong的时候减1,当引用计数变为零的时候,delete掉自己。 if (android_atomic_dec(&mCount) == 1) { delete static_cast<const T*>(this); } } inline int32_t getStrongCount() const { return mCount; } protected: inline ~LightRefBase() { } private: mutable volatile int32_t mCount;//引用计数控制变量。 };
LightRefBase类够简单吧?不过它是一个模板类,我们该怎么用它呢?下面给出一个例子,其中类A是从LightRefBase派生的,写法如下:
class A:public LightRefBase<A> //注意派生的时候要指明是LightRefBase<A>。 { public: A(){}; ~A(){}; };
5.2.5 题外话——三板斧的来历
从代码量上看,RefBase、sp和wp的代码量并不多,但里面的关系,尤其是flags的引入,曾一度让我眼花缭乱。当时,我确实很希望能自己调试一下这些例子,但在设备上调试native代码,需要花费很大的精力,即使是通过输出log的方式来调试也需要花很多时间。该怎么解决这一难题?
既然它的代码不多而且简单,那何不把它移植到台式机的开发环境下,整一个类似的RefBase呢?有了这样的构想,我便用上了Visual Studio。至于那些原子操作,Windows平台上有很直接的InterlockedExchangeXXX与之对应,真的是踏破铁鞋无觅处,得来全不费功夫!(在Linux平台上,不考虑多线程的话,将原子操作换成普通的非原子操作不是也可以吗?如果更细心更负责任的话,你可以自己用汇编来实现常用的原子操作,内核代码中有现成的函数,一看就会明白。)
如果把破解代码看成是攻城略地的话,我们必须学会灵活多变,而且应力求破解方法日臻极致!
5.3 Thread类及常用同步类分析
Thread类是Android为线程操作而做的一个封装。代码在Thread.cpp中,其中还封装了一些与线程同步相关的类(既然是封装,要掌握它,最重要的当然是掌握与Pthread相关的知识)。我们先分析Threa类,进而再介绍与常用同步类相关的知识。
5.3.1 一个变量引发的思考
Thread类虽说挺简单,但其构造函数中的那个canCallJava却一度让我感到费解。因为我一直使用的是自己封装的Pthread类。当发现Thread构造函数中竟然存在这样一个东西时,很担心自己封装的Pthread类会不会有什么重大问题,因为当时我还从来没考虑过Java方面的问题。
我们必须得了解它实际创建的线程函数是什么。Thread类真实的线程是创建在run函数中的。
1.一个变量,两种处理
先来看一段代码:
[-->Thread.cpp]
status_t Thread::run(const char* name, int32_t priority, size_t stack) { Mutex::Autolock _l(mLock); .... //如果mCanCallJava为真,则调用createThreadEtc函数,线程函数是_threadLoop。 //_threadLoop是Thread.cpp中定义的一个函数。 if (mCanCallJava) { res = createThreadEtc(_threadLoop,this, name, priority, stack, &mThread); } else { res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread); }
上面的mCanCallJava将线程创建函数的逻辑分为两个分支,虽传入的参数都有_threadLoop,但它们调用的函数却不同。先直接看mCanCallJava为true的这个分支,代码如下所示:
[-->Thread.h::createThreadEtc()函数]
inline bool createThreadEtc(thread_func_t entryFunction, void *userData, const char* threadName = “android:unnamed_thread”, int32_t threadPriority = PRIORITY_DEFAULT, size_t threadStackSize = 0, thread_id_t *threadId = 0) { return androidCreateThreadEtc(entryFunction, userData, threadName, threadPriority, threadStackSize, threadId) ? true : false; }
它调用的是androidCreateThreadEtc函数,相关代码如下所示:
// gCreateThreadFn是函数指针,它在初始化时和mCanCallJava为false时使用的是同一个 //线程创建函数。那么有地方会修改它吗? static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc; int androidCreateThreadEtc(android_thread_func_t entryFunction, void *userData,const char* threadName, int32_t threadPriority,size_t threadStackSize, android_thread_id_t *threadId) { return gCreateThreadFn(entryFunction, userData, threadName, threadPriority, threadStackSize, threadId); }
如果没有人修改这个函数指针,那么mCanCallJava就是虚晃一枪,并无什么作用。不过,代码中有的地方是会修改这个函数指针的指向的,请看——
2. zygote偷梁换柱
在本书4.2.1节的第2点所介绍的AndroidRuntime调用startReg的地方,就有可能修改这个函数指针,其代码如下所示:
[-->AndroidRuntime.cpp]
/*static*/ int AndroidRuntime::startReg(JNIEnv* env) { //这里会修改函数指针为javaCreateThreadEtc。 androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); return 0; }
如果mCanCallJava为true,则将调用javaCreateThreadEtc。那么,这个函数有什么特殊之处呢?来看其代码,如下所示:
[-->AndroidRuntime.cpp]
int AndroidRuntime::javaCreateThreadEtc( android_thread_func_t entryFunction, void* userData, const char* threadName, int32_t threadPriority, size_t threadStackSize, android_thread_id_t* threadId) { void** args = (void**) malloc(3 * sizeof(void*)); int result; args[0] = (void*) entryFunction; args[1] = userData; args[2] = (void*) strdup(threadName); //调用的还是androidCreateRawThreadEtc,但线程函数却换成了javaThreadShell。 result = androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args, threadName, threadPriority, threadStackSize, threadId); return result; }
[-->AndroidRuntime.cpp]
int AndroidRuntime::javaThreadShell(void* args) { ...... int result; //把这个线程attach到JNI环境中,这样这个线程就可以调用JNI的函数了。 if (javaAttachThread(name, &env) != JNI_OK) return -1; //调用实际的线程函数干活。 result = (*(android_thread_func_t)start)(userData); //从JNI环境中detach出来。 javaDetachThread(); free(name); return result; }
3. 费力能讨好
你明白mCanCallJava为true的目的了吗?它创建的新线程将:
在调用你的线程函数之前会attach到 JNI环境中,这样,你的线程函数就可以无忧无虑地使用JNI函数了。
线程函数退出后,它会从JNI环境中detach,释放一些资源。
Android为了dalvik的健康真是费尽心机呀。
4. 线程函数_threadLoop介绍
无论一分为二是如何处理的,最终都会调用线程函数_threadLoop,为什么不直接调用用户传入的线程函数呢?莫非_threadLoop会有什么暗箱操作吗?下面我们来看:
[-->Thread.cpp]
int Thread::_threadLoop(void* user) { Thread* const self = static_cast<Thread*>(user); sp<Thread> strong(self->mHoldSelf); wp<Thread> weak(strong); self->mHoldSelf.clear(); #if HAVE_ANDROID_OS self->mTid = gettid(); #endif bool first = true; do { bool result; if (first) { first = false; //self代表继承Thread类的对象,第一次进来时将调用readyToRun,看看是否准备好。 self->mStatus = self->readyToRun(); result = (self->mStatus == NO_ERROR); if (result && !self->mExitPending) { result = self->threadLoop(); } } else { /* 调用子类实现的threadLoop函数,注意这段代码运行在一个do-while循环中。 这表示即使我们的threadLoop返回了,线程也不一定会退出。 */ result = self->threadLoop(); } /* 线程退出的条件: 1)result 为false。这表明,如果子类在threadLoop中返回false,线程就可以 退出。这属于主动退出的情况,是threadLoop自己不想继续干活了,所以返回false。 读者在自己的代码中千万别写错threadLoop的返回值。 2)mExitPending为true,这个变量可由Thread类的requestExit函数设置,这种 情况属于被动退出,因为由外界强制设置了退出条件。 */ if (result == false || self->mExitPending) { self->mExitPending = true; self->mLock.lock(); self->mRunning = false; self->mThreadExitedCondition.broadcast(); self->mLock.unlock(); break; } strong.clear(); strong = weak.promote(); } while(strong != 0); return 0; }
关于_threadLoop,我们就介绍到这里。请读者务必注意下面一点:
threadLoop运行在一个循环中,它的返回值可以决定是否退出线程。
5.3.2 常用同步类
同步,是多线程编程中不可回避的话题,同时也是一个非常复杂的问题。这里只简单介绍一下Android提供的同步类。这些类,只对系统提供的多线程同步函数(这种函数我们称为Raw API)进行了面向对象的封装,读者必须先理解Raw API,然后才能真正掌握其具体用法。
提示 要了解Windows下的多线程编程,有很多参考资料,而有关Linux下完整系统阐述多线程编程的书籍目前较少,这里推荐一本含金量较高的著作《Programming with POSIX Thread》(本书只有英文版,由Addison-Wesley出版)。
Android提供了两个封装好的同步类,它们是Mutex和Condition。这是重量级的同步技术,一般内核都会有对应的支持。另外,OS还提供了简单的原子操作,这些也算是同步技术中的一种。下面分别来介绍这三种东西。
1. 互斥类—Mutex
Mutex是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源。在《Windows核心编程》一书中,对于这种互斥访问有一个很形象的比喻:想象你在飞机上如厕,这时卫生间的信息牌上显示“有人”,你必须等里面的人出来后才可进去。这就是互斥的含义。
下面来看Mutex的实现方式,它们都很简单。
(1)Mutex介绍
其代码如下所示:
[-->Thread.h::Mutex的声明和实现]
inline Mutex::Mutex(int type, const char* name) { if (type == SHARED) { //type如果是SHARED,则表明这个Mutex支持跨进程的线程同步。 //以后我们在Audio系统和Surface系统中会经常见到这种用法。 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&mMutex, &attr); pthread_mutexattr_destroy(&attr); } else { pthread_mutex_init(&mMutex, NULL); } } inline Mutex::~Mutex() { pthread_mutex_destroy(&mMutex); } inline status_t Mutex::lock() { return -pthread_mutex_lock(&mMutex); } inline void Mutex::unlock() { pthread_mutex_unlock(&mMutex); } inline status_t Mutex::tryLock() { return -pthread_mutex_trylock(&mMutex); }
关于Mutex的使用,除了初始化外,最重要的是lock和unlock函数的使用,它们的用法如下:
- 要想独占卫生间,必须先调用Mutex的lock函数。这样,这个区域就被锁住了。如果这块区域之前已被别人锁住,lock函数则会等待,直到可以进入这块区域为止。系统保证一次只有一个线程能lock成功。
- 当你“方便”完毕,记得调用Mutex的unlock以释放互斥区域。这样,其他人的lock才可以成功返回。
- 另外,Mutex还提供了一个trylock函数,该函数只是尝试去锁住该区域,使用者需要根据trylock的返回值来判断是否成功锁住了该区域。
Mutex类确实比Raw API方便好用,不过还是稍显麻烦。
(2)AutoLock介绍
AutoLock类是定义在Mutex内部的一个类,它其实是一帮“懒人”搞出来的,为什么这么说呢?先来看看使用Mutex有多麻烦:
- 显示调用Mutex的lock。
- 在某个时候记住要调用该Mutex的unlock。
以上这些操作都必须一一对应,否则会出现“死锁”!在有些代码中,如果判断分支特别多,你会发现unlock这句代码被写得比比皆是,如果稍有不慎,在某处就会忘了写它。有什么好办法能解决这个问题吗?终于有人想出来一个好办法,就是充分利用了C++的构造和析构函数,只需看一看AutoLock的定义就会明白。代码如下所示:
[-->Thread.h Mutex::Autolock声明和实现]
class Autolock { public: //构造的时候调用lock。 inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); } //析构的时候调用unlock。 inline ~Autolock() { mLock.unlock(); } private: Mutex& mLock; };
AutoLock的用法很简单:
- 先定义一个Mutex,如 Mutex xlock。
- 在使用xlock的地方,定义一个AutoLock,如 AutoLock autoLock(xlock)。
由于C++对象的构造和析构函数都是自动被调用的,所以在AutoLock的生命周期内,xlock的lock和unlock也就自动被调用了,这样就省去了重复书写unlock的麻烦,而且lock和unlock的调用肯定是一一对应的,这样就绝对不会出错。
2. 条件类—Condition
多线程同步中的条件类对应的是下面这种使用场景:
线程A做初始化工作,而其他线程比如线程B、C必须等到初始化工作完后才能工作,即线程B、C在等待一个条件,我们称B、C为等待者。
当线程A完成初始化工作时,会触发这个条件,那么等待者B、C就会被唤醒。触发这个条件的A就是触发者。
上面的使用场景非常形象,而且条件类提供的函数也非常形象,它的代码如下所示:
[-->Thread.h:: Condition的声明和实现]
class Condition { public: enum { PRIVATE = 0, SHARED = 1 }; Condition(); Condition(int type);//如果type是SHARED,表示支持跨进程的条件同步 ~Condition(); //线程B和C等待事件,wait这个名字是不是很形象呢? status_t wait(Mutex& mutex); //线程B和C的超时等待,B和C可以指定等待时间,当超过这个时间,条件却还不满足,则退出等待。 status_t waitRelative(Mutex& mutex, nsecs_t reltime); //触发者A用来通知条件已经满足,但是B和C只有一个会被唤醒。 void signal(); //触发者A用来通知条件已经满足,所有等待者都会被唤醒。 void broadcast(); private: #if defined(HAVE_PTHREADS) pthread_cond_t mCond; #else void* mState; #endif }
声明很简单,定义也很简单,代码如下所示:
inline Condition::Condition() { pthread_cond_init(&mCond, NULL); } inline Condition::Condition(int type) { if (type == SHARED) {//设置跨进程的同步支持。 pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_cond_init(&mCond, &attr); pthread_condattr_destroy(&attr); } else { pthread_cond_init(&mCond, NULL); } } inline Condition::~Condition() { pthread_cond_destroy(&mCond); } inline status_t Condition::wait(Mutex& mutex) { return -pthread_cond_wait(&mCond, &mutex.mMutex); } inline status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) { #if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE) struct timespec ts; ts.tv_sec = reltime/1000000000; ts.tv_nsec = reltime%1000000000; return -pthread_cond_timedwait_relative_np(&mCond, &mutex.mMutex, &ts); ...... //有些系统没有实现POSIX的相关函数,所以不同的系统需要调用不同的函数。 #endif } inline void Condition::signal() { pthread_cond_signal(&mCond); } inline void Condition::broadcast() { pthread_cond_broadcast(&mCond); }
可以看出,Condition的实现全是凭借调用了Raw API的pthread_cond_xxx函数。这里要重点说明的是,Condition类必须配合Mutex来使用。什么意思?
在上面的代码中,不论是wait、waitRelative、signal还是broadcast的调用,都放在一个Mutex的lock和unlock范围中,尤其是wait和waitRelative函数的调用,这是强制性的。
来看一个实际的例子,加深一下对Condition类和Mutex类的印象。这个例子是Thread类的requestExitAndWait,目的是等待工作线程退出,代码如下所示:
[-->Thread.cpp]
status_t Thread::requestExitAndWait() { ...... requestExit(); //设置退出变量mExitPending为true。 Mutex::Autolock _l(mLock);//使用Autolock,mLock被锁住。 while (mRunning == true) { /* 条件变量的等待,这里为什么要通过while循环来反复检测mRunning? 因为某些时候即使条件类没有被触发,wait也会返回。关于这个问题,强烈建议读者阅读 前面推荐的《Programming with POSIX Thread》一书。 */ mThreadExitedCondition.wait(mLock); } mExitPending = false; //退出前,局部变量Mutex::Autolock _l的析构会被调用,unlock也就会被自动调用。 return mStatus; }
那么,什么时候会触发这个条件呢?是在工作线程退出前。其代码如下所示:
[-->Thread.cpp]
int Thread::_threadLoop(void* user) { Thread* const self = static_cast<Thread*>(user); sp<Thread> strong(self->mHoldSelf); wp<Thread> weak(strong); self->mHoldSelf.clear(); do { ...... result = self->threadLoop();//调用子类的threadLoop函数。 ...... //如果mExitPending为true,则退出。 if (result == false || self->mExitPending) { self->mExitPending = true; //退出前触发条件变量,唤醒等待者。 self->mLock.lock();//lock锁住。 //mRunning的修改位于锁的保护中。如果你阅读了前面推荐的书,这里也就不难理解了。 self->mRunning = false; self->mThreadExitedCondition.broadcast(); self->mLock.unlock();//释放锁。 break;//退出循环,此后该线程函数会退出。 } ...... } while(strong != 0); return 0; }
关于Android多线程的同步类,暂时介绍到此吧。当然,这些类背后所隐含的知识及技术是读者需要倍加重视的。
3. 原子操作函数介绍
什么是原子操作?所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,原子操作是最小的执行单位。
上面这句话放到代码中是什么意思?请看一个例子:
[-->例子]
static int g_flag = 0; //全局变量g_flag static Mutex lock ;//全局的锁 //线程1执行thread1。 void thread1() { //g_flag递减,每次操作前锁住。 lock.lock(); g_flag--; lock.unlock(); } //线程2中执行thread2函数。 void thread2() { lock.lock(); g_flag++; //线程2对g_flag进行递增操作,每次操作前要取得锁。 lock.unlock(); }
为什么需要Mutex来帮忙呢?因为g_flags++或g_flags--操作都不是原子操作。从汇编指令的角度看,C/C++中的一条语句对应了数条汇编指令。以g_flags++操作为例,它生成的汇编指令可能就是以下三条:
- 从内存中取数据到寄存器。
- 对寄存器中的数据进行递增操作,结果还在寄存器中。
- 寄存器的结果写回内存。
这三条汇编指令,如果按正常的顺序连续执行是没有问题的,但在多线程时就不能保证了。例如,线程1在执行第一条指令后,线程2由于调度的原因,抢在线程1之前连续执行完了三条指令。这样,线程1继续执行指令时,它所使用的值就不是线程2更新后的值,而是之前的旧值。再对这个值进行操作便没有意义了。
在一般情况下,处理这种问题可以使用Mutex来加锁保护,但Mutex的使用方法比它所要保护的内容还要复杂,例如,锁的使用将导致从用户态转入内核态,有较大的浪费。那么,有没有简便些的办法让这些加、减等操作不被中断呢?
答案是肯定的,但这需要CPU的支持。在X86平台上,一个递增操作可以用下面的内嵌汇编语句来实现:
#define LOCK "lock;" INT32 InterlockedIncrement(INT32* lpAddend) { /* 这是我们在Linux平台上实现Windows API时使用的方法。 其中在SMP系统上,LOCK定义成"lock;"表示锁总线,这样同一时刻就只能有一个CPU访问总线了。 非SMP系统,LOCK定义成空。由于InterlockedIncrement要返回递增前的旧值,所以我们 使用了xaddl指令,它先交换源和目的的操作数,再进行递增操作。 */ INT32 i = 1; __asm__ __volatile__( LOCK "xaddl %0, %1" :"+r" (i), "+m" (*lpAddend) : : "memory"); return *lpAddend; }
Android提供了相关的原子操作函数。这里有必要介绍一下各个函数的作用。
[-->Atomic.h],注意该文件位于system/core/include/cutils目录中。
//原子赋值操作,结果是*addr=value。 void android_atomic_write(int32_t value, volatile int32_t* addr); //下面所有函数的返回值都是操作前的旧值。 //原子加1和原子减1。 int32_t android_atomic_inc(volatile int32_t* addr); int32_t android_atomic_dec(volatile int32_t* addr); //原子加法操作,value为被加数。 int32_t android_atomic_add(int32_t value, volatile int32_t* addr); //原子“与”和“或”操作。 int32_t android_atomic_and(int32_t value, volatile int32_t* addr); int32_t android_atomic_or(int32_t value, volatile int32_t* addr); /* 条件交换的原子操作。只有在oldValue等于*addr时,才会把newValue赋值给*addr。 这个函数的返回值须特别注意。返回值非零,表示没有进行赋值操作。返回值为零,表示 进行了原子操作。 */ int android_atomic_cmpxchg(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);
有兴趣的话,读者可以对上述函数的实现进行深入研究,其中:
- X86平台的实现在system/core/libcutils/Atomic.c中,注意其代码在#elif defined(__i386__) || defined(__x86_64__)所包括的代码段内。
- ARM平台的实现在system/core/libcutils/atomic-android-arm.S汇编文件中。
原子操作的最大好处在于避免了锁的使用,这对整个程序运行效率的提高有很大帮助。目前,在多核并行编程中,最高境界就是完全不使用锁。当然,它的难度可想而知是巨大的。
5.4 Looper和Handler类分析
应用程序而言,Android系统中Java的应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下:
- 有一个消息队列,可以往这个消息队列中投递消息。
- 有一个消息循环,不断从消息队列中取出消息,然后处理。
我们用图5-1来展示这个工作过程:
图5-1 线程和消息处理的原理图
从图中可以看出:
- 事件源把待处理的消息加入到消息队列中,一般是加至队列尾,一些优先级高的消息也可以加至队列头。事件源提交的消息可以是按键、触摸屏等物理事件产生的消息,也可以是系统或应用程序本身发出的请求消息。
- 处理线程不断从消息队列头中取出消息并处理,事件源可以把优先级高的消息放到队列头,这样,优先级高的消息就会首先被处理。
在Android系统中,这些工作主要由Looper和Handler来实现:
- Looper类,用于封装消息循环,并且有一个消息队列。
- Handler类,有点像辅助类,它封装了消息投递、消息处理等接口。
Looper类是其中的关键。先来看看它是怎么做的。
5.4.1 Looper类分析
我们以Looper使用的一个常见例子来分析这个Looper类。
[-->例子1]
//定义一个LooperThread。 class LooperThread extends Thread { public Handler mHandler; public void run() { //① 调用prepare。 Looper.prepare(); ...... //② 进入消息循环。 Looper.loop(); } } //应用程序使用LooperThread。 { ...... new LooperThread().start();//启动新线程,线程函数是run }
上面的代码一共有两个关键调用(即①和②),我们对其逐一进行分析。
1. 准备好了吗
第一个调用函数是Looper的prepare函数。它会做什么工作呢?其代码如下所示:
[-->Looper.java]
public static final void prepare() { //一个Looper只能调用一次prepare。 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //构造一个Looper对象,设置到调用线程的局部变量中。 sThreadLocal.set(new Looper()); } //sThreadLocal定义 private static final ThreadLocal sThreadLocal = new ThreadLocal();
ThreadLocal是Java中的线程局部变量类,全名应该是Thread Local Variable。我觉得它的实现和操作系统提供的线程本地存储(TLS)有关系。总之,该类有两个关键函数:
- set:设置调用线程的局部变量。
- get:获取调用线程的局部变量。
根据上面的分析可知,prepare会在调用线程的局部变量中设置一个Looper对象。这个调用线程就是LooperThread的run线程。先看看Looper对象的构造,其代码如下所示:
[-->Looper.java]
private Looper(){ //构造一个消息队列。 mQueue = new MessageQueue(); mRun = true; //得到当前线程的Thread对象。 mThread = Thread.currentThread(); }
1 楼 loujiawen924 2011-11-03 22:47