引言:老罗的《Android系统源代码情景分析》一上来就提到了一个名为智能指针的概念,这个东西对于理解android应用系统框架很有帮助,无论是FrameWork层的C++代码亦或者kernel层中会经常用到这个概念,它也是binder引用计数的基础,对于理解binder通信架构和生命周期也很有帮助,所以不妨以书为引做个总结。


so? what is 智能指针?(这里提下,这个指针不是指*,指的是一个对象,但是它引用了一个实际使用的对象)

用书中原话解释:智能指针式一种能够自动维护对象引用计数的技术。
具体一点解释,大家都知道C++需要使用大量的指针,指针最容易出现错误的地方就是忘记释放其指向的对象所占的内存导致内存泄漏。那么为了避免这种情况,Android系统提供了C++智能指针通过引用计数技术来维护对象的生命周期。(下面稍微解释一下引用计数技术,我相信很多人都了解过)

  • 引用计数法:这种算法的思路是如果某一个对象被别的对象,那么就把他们引用计数器加上1,这样当进行垃圾回收时如果判断该引用的数量为0,此时就代表没有进行任何对象对其进行引用,此时就进行回收。

  • 缺陷:但是这种技术会出现一个问题就是两个对象相互引用的时候会出现“死锁”的情况。比如A引用B,B引用A。当对象A不再使用需要释放它所占的内存时,由于A仍然被B引用所以无法释放,只能等待B释放这个引用,同样对B来说一样的问题。所以会造成相互等待,这个和Java中的锁同步问题一个道理,A对象wait()了自己等待B对象唤醒,B对象也wait()了自己等待A对象唤醒自己。就如两个睡美人都在等待对方叫醒自己一样造成死锁状态。

  • 解决方法: 在java中为了解决这个问题引入了引用链方法,这里仅仅提一下这个概念–“JVM采用GC Roots可达性来决定是否会被GC回收”,可以参考《深入JVM虚拟机》一书。

那么Android的智能指针是怎么解决这个问题的呢?

这里先介绍一种较为复杂的引用计数方法,这种方法将对象的引用计数分为强引用和若引用计数两种,但是对象的生命周期只受强引用计数控制。这种解决方案以”父子“关系将对象很有意思的关联了起来,即”父”对象通过强引用计数引用”子”对象,“子”对象通过弱引用计数引用“父”对象,但是很明显按照传统美德只有父亲管着儿子,所以当“子”对象想要释放自己时由于它还收到“父”对象的管制无法释放自己;但是“父”对象想要释放自己时可以轻易释放自己,此时由于“父”不存在了,“子”对象不受强引用计数的管制了就可以释放自己了。

好的介绍完了这些背景可以公布答案了,答案就是Android提供了三种类型的指针,下文会分别介绍。

  • 轻量级指针(Light Pointer)
  • 强指针(Strong Pointer)
  • 弱指针(Weak Pointer)

1.轻量级指针

这里不多提轻量级指针,因为这种指针式通过简单引用计数技术来维护对象生命周期的.(个人觉得还是会有相互引用的风险产生,所以并没有懂使用这个指针的意义在哪儿?也许是相比强指针和弱指针其效率更高吧)。关于它只需知道3点:

  • 第一点使用它需要继承LightRefBase(模板类) public LightClass: public LightRefBase<LightClass>

  • 第二点LightRefBase类只有一个成员变量mCount用来描述一个对象的引用计数值。

  • 第三点需要知道轻量级指针的实现类和强指针的实现类是同一个类sp。

2.强指针

2.1 实现

与轻量级指针不同,强指针不是直接使用一个整数来维护对象的引用计数的,而是使用一个weakref_impl对象,这个对象是继承RefBase类(一个类要使用强指针和弱指针必须继承RefBase)中的内部类weakref_type类,其中weakref_type仅仅只定义了引用计数维护接口,具体实现是weakref_impl。(具体关系如下图,图是手码的,一个是继承关系,一个是引用关系)
继承关系 这里说一下成员变量mFlags的作用,mFlags这个标志位有三种取值:

  • 0: 表示对象的生命周期只受强引用计数影响;默认就是这个。
  • 1(OBJECT_LIFETIME_WEAK): 表示对象的生命周期同时受强引用计数和弱引用计数影响
  • OBJECT_LIFETIME_FOREVER: 表示对象的生命周期完全不受强引用计数和弱引用计数的影响。(这个地方我想说一下,我实践的时候发现并没有这个标志位,可能是后来的android版本在基类里面取消了这个标识位,具体我还没有仔细查最新的源码,会继续补充。)

原书里面解释强指针或弱指针均涉及源代码分析,这里我尝试用自己的语言总结一下重要的部分

2.2 分析

2.2.1 incStrong

RefBaseincStrong函数干了哪些事情呢?(主要有三步,第三步是第一次强引用的一些逻辑处理,这里不分析)

  • 增加弱引用计数(这个看起来好像与函数的名字有点相互违背,这个后面会解释)
    具体过程:通过mRefsincWeak方法来增加对象的弱引用计数(可以配合类图理解),mRefsWeakref_impI类型的,Weakref_impl又继承了inWeak方法,实际上调用的是weakref_type的方法

  • 增加强引用计数。
    通过android_atomic_inc函数增加强引用计数值(返回增加前的值,这里注意是之前)

可以看出强指针类增加对象的强引用计数的同时也会增加弱引用计数,即一个对象的弱引用计数一定是大于或者等于它的强引用计数的。(sp的构造函数就干了这么些事情)

2.2.2 decStrong

那么sp的析构函数干了什么事情呢?即decStrong

  • 减少对象的强引用计数
    当强引用计数为0时(实际上不是0,这里用0好解释),即不再被强指针引用时。此时需要判断标识位mFlags(上面提过)是否为1,如果不为1,就会释放对象所占的内存,同时也会导致RefBase类的析构函数调用。

  • 减少对象的弱引用计数,
    一旦发现弱引用计数为0时,把引用计数对象mRefsweakref_impl类型)也释放掉(前面提过,建议回头看看方便理解)。前面说过,一个对象的弱引用计数一定大于或者等于强引用计数的,当强引用计数为0时,会释放掉RefBase对象,但当此时弱引用计数大于0时,不能将mRefs也释放掉,因为还有其他的弱指针通过weakref_impl对象来引用实际的对象。

3.弱指针

弱指针同样从RefBase类继承下来,因为RefBase提供了弱引用计数器。弱指针类的实现类为wp。弱指针使用的是类型为weakref_type*的成员变量m_refs维护对象的弱引用计数。

弱指针和强指针有一个很大的区别,就是弱指针不可以直接操作它所引用的对象,因为它所引用的对象可能是不受弱引用计数控制的,即它所引用的对象可能是一个无效的对象。因此,如果需要操作一个弱指针所引用的对象,那么就需要将这个弱指针升级为强指针,这是通过它的成员函数promote来实现的。如果升级成功,就说明该弱指针所引用的对象还没有被销毁,可以正常使用。

下面着重介绍wp的promote函数。先来看两段源代码代码

RefBase.h

参数p指向对象的地址,而参数refs指向该对象内部的一个弱引用计数器对象。只有在对象地址不为null的情况下,才会调用它内部的弱引用计数器对象的成员函数attempIncStrong来试图增加该对象的强引用计数。如果能够成功增加对象的强引用计数,那么就可以成功地把一个弱指针升级为一个强指针。

attempIncStrong看着是不是很熟悉,可以从之前的图中找到。 这个成员函数试图增加目标对象的强引用计数,但是有可能会增加失败,因为目标对象可能已经被释放了,或者该目标对象不允许使用强指针引用它。

attempIncStrong中有个有意思的逻辑:

之前提过增加对象强引用计数时,同时也会增加该对象的弱引用计数。

  • 1.先调用成员函数incWeak来增加对象的弱引用计数

  • 2.如果后面增加对象的强引用计数失败,则调用decWeak来减少对象的弱引用计数。

一个弱指针所引用的对象可能处于两种状态。(下面均摘自原文)

第一种:该对象同时也被其他强指针对象所引用,此时可以安全地将这个弱指针升级为强指针。
第二种:该对象没有被任何强指针引用。这里情况就比较复杂了需要根据对象生命周期来判断。

  • 如果对象生命周期只受强引用计数影响,那么就可以成功将该弱指针升级为强指针。因为它受强引用计数影响,而此时该对象又没有被强指针引用过,那么它必然不会被释放。
  • 如果只受弱引用计数影响,首先我们可以确定对象现在一定是存在的,因为现在有一个弱指针引用它。但是,这种情况需要进一步调用对象的成员函数onIncStrongAttempted来确认对象是否允许强指针引用它。如果返回为true说明允许则成功将该弱指针升级为强指针。如果返回为false,则说明升级失败。