Android智能指针浅析

长久以来,C++中的内存管理问题一直让人头疼,空指针,野指针,内存泄露。。。。。。C++程序员看到这样的问题就是各种头大!这样的问题往往很难解决,尤其是代码架构比较庞大或者复杂的时候。但是同样是面向对象的JAVA语言却没有这个问题,为什么呢?因为java有GC,也就是垃圾回收而C++没有。C++的做法是:程序员必须做到在new一个对象之后当不需要使用它的时候必须delete这个对象。看来很好,没有问题是吧?但是,在实际的项目中,就是会有人忘记去delete这个对象,或者随意delete一个对象,导致依赖这个对象的别的模块发生野指针错误。这几乎是经常发生的,而且是很难避免的。
Android中除了framework之上是Java语言写就的,其余native层到kernel之上这部分基本都是C++写成的。Android的这部分代码量还是比较庞大的,那么它是怎么解决上面提到的C++内存问题的呢?为此,android引入了一个”智能指针“的概念,其实这个概念并不是android提出的,这个概念在C++的boost中就有的,google只是拿过来做了一个精简版本罢了。对C++ boost的智能指针感兴趣的可以看这个博客:
http://blog.csdn.net/xt_xiaotian/article/details/5714477
这里我们专注了解android中的智能指针是怎么实现的。Android的智能指针其实是比较复杂的,一时半会也说不清楚,在正式分析android的智能指针代码之前,我们继续上面的C++内存问题探讨。
前面提到,在C++中需要new和delete配对使用,而且是程序员手动显式执行。这对于程序员的要求是很高的,程序员需要知道什么时候new了一个对象,什么时候这个对象肯定是不需要的了然后采取delete它。这似乎有些理想,因为很多情况下不是这样的,有的时候我们new一个对象然后这个对象就是给别的模块使用的,别的模块又是由别的小组或者公司维护的,他们很可能不会及时delete这个对象。那有什么办法可以解决这个问题呢?我们很容易会想到,专门造一个模块,这个模块来关于众多产生的对象,然后同时负责这些对象的销毁。但是,这个方案有一个问题,如果需要产生多个管理模块的情况下,怎么保证多个模块之间的引用是一致的呢?也就是说可能管理模块A持有某个对象的引用,然后某块B也持有这个对象的引用,这样的话这两个模块之间怎么协调这个对象的引用问题呢?这是比较复杂的,也会有很多潜在的问题。那还有什么办法呢?要解决这个问题需要理解一个事情,那就是对象的引用计数问题,这是java的GC实现的基础。所谓引用计数就是这个对象在几个地方被使用了,比如:

Object a = new Object();
Object b = a;

这两行代码中,新new出来的对象有两个引用计数,引用a和引用b同时指向同一块内存空间。那什么时候我们就不需要这个对象了呢?那就是他不需要的时候?这有点像废话,但是事实就是这样,只要当这个对象的引用计数等于0的时候,这个对象就不需要了。这样以来,这个问题就转化为:找到谁对这个对象的引用技术最为清楚?谁最直接了解这个对象的引用计数?对了!答案就是这个对象它自己!!!什么?你要这个对象自己delete自己,听起来有点荒谬,但是android中的智能指针就是这么做的。

LightRefBase

这是个android中给出的一个轻量级的智能指针解决方案,使用的思想就是我们上面分析的思想。为了让对象可以做到自己delete自己,我们需要给所有的对象做一个公共的基类,这个LightRefBase就是这个基类。在分析这个代码之前,我们先看一下,这个东西我们怎么使用,以下是一个使用它的demo:

/*************************************************************************
    > File Name: lightpointer.cpp
    > Author: Baniel Gao
    > Mail: [email protected] 
    > Created Time: Fri 22 Apr 2016 03:27:28 PM CST
 ************************************************************************/

#include <stdio.h>
// 包含需要的头文件
#include <utils/RefBase.h>

using namespace android;

// 目标类必须是LightRefBase的子类
class LightClass : public LightRefBase<LightClass>
{
    public:
        LightClass()
        {
            printf("create instance of lightclass. \n");
        }

        virtual ~LightClass()
        {
            printf("destory instance of lightclass. \n");
        }
};

int main(int argc, char** argv)
{
    // new一个对象,并且将指针指向它
    LightClass* ptr = new LightClass();

    // 打印引用计数,就是看看有多少个地方使用了它,当然这里还没有显式引用,结果应该是0.
    printf("1. Light ref count: %d. \n", ptr->getStrongCount());
    // sp<>这样的引用就是显式引用,是的一个指针指向它,然后我们看下现在的引用计数是多少
    // 按照推理,应该是1.
    sp<LightClass> ptrOut = ptr;
    printf("2. Light ref count: %d. \n", ptr->getStrongCount());

    // 代码块,在代码块内部再弄一个指针指向这个对象,然后再看一下引用计数
    {
        sp<LightClass> ptrInner = ptr;
        printf("3. Light ref count: %d. \n", ptr->getStrongCount());
    }

    // 再代码块的外面,我们看一下引用计数。由于在代码块的外面,代码块的内部的那个
    // 指向指针应该不存在了,所有引用计数应该比代码块内部少一个。
    printf("4. Light ref count: %d. \n", ptr->getStrongCount());

    // 将最后一个引用置空, 并且睡眠两秒,等待看看这个对象会不会自动销毁。
    ptrOut = NULL;
    printf("5. Light ref count: %d. \n", ptr->getStrongCount());

    printf("Wre are going to sleep. \n");
    sleep(2// 2s后我们直接退出程序
    printf("Wre are going to exit. \n");
    return 0;
}

大家先不用管具体的代码细节是怎么回事,先看看运行的结果。为了运行这个程序我们需要编译它,这里我把它编译成android手机上可以运行的二进制文件,下面是Android.mk文件:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE := lightpointer

LOCAL_SRC_FILES := lightpointer.cpp

LOCAL_SHARED_LIBRAIES :=    \
    libcutils   \
    libutils

include $(BUILD_EXECUTABLE)

可执行文件名称就是lightpointer,并且存放于手机的system/bin下,以下是运行效果:
这里写图片描述
可以看到和我们的猜想是基本一致的。首先,我们new一个对象,但是没有显式引用所以引用计数当然是0,然后我们使用sp类(这个类先不去理会稍后分析)引用它,然后它的计数就是1了,再然后再代码块中再次引用它,发现计数是2,然后在代码块的外部发现对象引用计数还是1,因为代码块执行完毕了,刚才内部的那个指针不存在了。最后我们再把最后一个指针置空,然后这个对象由于引用计数为0了就自动销毁了。看到了没有,是不是有点JAVA的意思啊?使用很简单是不?
现在我们来分析一下刚才代码再android系统内发生了什么,首先我们看一下LightRefBase这个类,代码路径:/system/core/include/utils/RefBase.h
它的代码如下
这里写图片描述
我们看到,这个类的实现还是比较简答的,整体上公共方法就3个:
incStrong
decStrong
getStrongCount //刚才我们一直在用的方法
我们所有的目标类都应该是它的子类,首先我们应该看一下它的构造器。它的构造器是比较简单的,只是将mCount这个变量初始化为0,从字面意思我们也可以看出这个变量就是表示实际的引用数目。并且我们刚才一直使用的getStrongCount方法也就是把它直接return了。接下来我们看到incStrong和decStrong两个方法,从字面上可以看出来,这两个方法分别是增加引用计数和减少引用计数。我们先看一下增加引用技术吧,他做的事情比较简单,直接使用android的util函数库中的android_atomic_inc来将mCount变量加1,这个函数的加1操作是原子操作,从名字上也可以看出,这是为了线程安全考虑的。然后我们再看一下减少引用技术的操作,这个也很简单,首先使用android_atomic_dec这个原子操作函数将mCount减1,然后根据这个函数的返回值判断是不是最后一个引用计数,如果是就直接delete这个对象,如果不是就跳过。这里需要说明一下,android_atomic_dec这个函数的返回值是mCount减少之前的值相当于mCount–的值,所以这里判断的条件是1而不是0。但是现在我们有一个问题这些增加和减少引用计数的方法是哪里调用的呢?还记得我们在上面那个代码中的sp类吗?对,就是它调用的!我们来看一下它的代码:
它的代码路径:/system/core/include/utils/StrongPointer.h,代码内容如下:
这里写图片描述
可以看到,它的内部方法基本很少,大部分都是运算符的重载。我们先看一下它的构造器:
这里写图片描述
首先,这个类是一个模板类,具体的对象类型是一个泛型。我们可以看到在它的构造器中将目标对象的引用赋值给m_ptr指针,然后如果目标对象的指针不为空的话直接调用它的incStrong方法将引用计数值加1。这是使用构造器传递指针的方式进行初始化的方式,我们上面的代码并没有使用这个方式,我们使用的是它的=号运算符重载:
这里写图片描述
可以看到这里才是我们上面代码使用的地方,这里和构造器逻辑基本类似,也是直接调用incStrong方法将引用加1。这里需要说明一下,这个方法考虑了sp指针重新赋值的情况,也就是说如果原本的m_ptr不为空,即原本是有指向的,现在需要先减少目标对象的引用计数,然后再将新的指针赋值给它。也就是说只要我们执行这条代码:

sp<LightClass> ptrOut = ptr;

就会执行上面的代码,然后引用计数就加1。相反如果我们把指针赋值为NULL或者别的值那么目标对象的引用计数就会减少。
这样一来,目标对象只要继承自LightRefBase类,并且我们使用sp类指针对引用就能实现对象的自动销毁,而不用再手动地执行delete了。
以上就是LightRefBase的分析,这是android的轻量级智能指针方案,实现和使用都比较简单,代码清爽不繁杂,这也就是他为什么叫轻量级的原因。

RefBase

如果说上面的LightRefBase是轻量级的,那么RefBase就应该是重量级的了,它的名字中少了light。Android为神马要引入这个类呢?想一下这样一个场景,现在有两个对象:A和B,对象A中有B的引用,因此B的引用等于1;对象B中有A的引用,因此对象A的引用对于1;现在问题来了,这两个对象和外界的任何对象都没有关系,也就说除了A和B两者之间有关系,别人和他们都没有关系!现在他们就是多余的了,应该被销毁!但是由于A和B的引用计数都是1,不为0,因此使用我们上面的方案解决不了了!还是会有内存泄露问题!怎么办呢??解决的办法是这样的,将引用分类,分为两类:强引用和弱引用。强引用就是我们上面使用的那种,弱引用是什么呢?弱引用从字面上引用的力度比强引用要弱,事实确实是这样。弱引用弱在哪里呢?弱在保证使用对象的可靠性上。这么说有点抽象,具体来说吧,像上面说的那个问题,如果A对象对B对象的引用是强引用的话,那么B对象对A对象必须是弱引用,否则还有刚才说的循环引用的问题。对象的销毁,关注的是对象的强引用,而不是对象的弱引用,也就是说如果对象的强引用为0的话不管对象的弱引用是多少直接delete掉!这就是弱引用弱的地方,也就是说你想使用的对象不一定存在呢!!另外,还有一个问题,那就是既然对象可能不存了,弱引用怎么使用这个对象呢?面对这个问题,有这样一个规定:
1. 弱引用在使用之前不如先升级为强引用才行。
如果对象不存在了,那么升级弱引用是失败的,自然就可以避免引用对象存在不确定性的问题了。说了这么多,我们现在来分析一下RefBase的代码,它的代码和LightRefBase类存在于一个文件中: /system/core/include/utils/RefBase.h

class RefBase
{
public:
            void            incStrong(const void* id) const;
            void            decStrong(const void* id) const;

            void            forceIncStrong(const void* id) const;

            //! DEBUGGING ONLY: Get current strong ref count.
            int32_t         getStrongCount() const;

    class weakref_type
    {
    public:
        RefBase*            refBase() const;

        void                incWeak(const void* id);
        void                decWeak(const void* id);

        // acquires a strong reference if there is already one.
        bool                attemptIncStrong(const void* id);

        // acquires a weak reference if there is already one.
        // This is not always safe. see ProcessState.cpp and BpBinder.cpp
        // for proper use.
        bool                attemptIncWeak(const void* id);

        //! DEBUGGING ONLY: Get current weak ref count.
        int32_t             getWeakCount() const;

        //! DEBUGGING ONLY: Print references held on object.
        void                printRefs() const;

        //! DEBUGGING ONLY: Enable tracking for this object.
        // enable -- enable/disable tracking
        // retain -- when tracking is enable, if true, then we save a stack trace
        //           for each reference and dereference; when retain == false, we
        //           match up references and dereferences and keep only the 
        //           outstanding ones.

        void                trackMe(bool enable, bool retain);
    };

            weakref_type*   createWeak(const void* id) const;

            weakref_type*   getWeakRefs() const;

            //! DEBUGGING ONLY: Print references held on object.
    inline  void            printRefs() const { getWeakRefs()->printRefs(); }

            //! DEBUGGING ONLY: Enable tracking of object.
    inline  void            trackMe(bool enable, bool retain)
    { 
        getWeakRefs()->trackMe(enable, retain); 
    }

    typedef RefBase basetype;

protected:
                            RefBase();
    virtual                 ~RefBase();

    //! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

            void            extendObjectLifetime(int32_t mode);

    //! Flags for onIncStrongAttempted()
    enum {
        FIRST_INC_STRONG = 0x0001
    };

    virtual void            onFirstRef();
    virtual void            onLastStrongRef(const void* id);
    virtual bool            onIncStrongAttempted(uint32_t flags, const void* id);
    virtual void            onLastWeakRef(const void* id);

private:
    friend class weakref_type;
    class weakref_impl;

                            RefBase(const RefBase& o);
            RefBase&        operator=(const RefBase& o);

private:
    friend class ReferenceMover;

    static void renameRefs(size_t n, const ReferenceRenamer& renamer);

    static void renameRefId(weakref_type* ref,
            const void* old_id, const void* new_id);

    static void renameRefId(RefBase* ref,
            const void* old_id, const void* new_id);

        weakref_impl* const mRefs;
};

这个看起来有些复杂,下面我们来一步一步分析它的实现。首先他和LightRefBase一样,只是实现的功能不一样而已,都是目标类的基类,你的目标类必须是这个类的子类。接下来我们以强指针和弱指针为两条线路分析一下。

sp(强指针)

很多人刚开始看到sp的时候,都会以为sp是smart pointer的缩写,其实不是他是strong pointer的缩写。sp类的实现我们在前面分析LightRefBase类的时候已经分析过了,它的代码还是比较简单,主要就是一些运算符的重载。这里我们重点分析一下RefBase和sp的协作。前面提到,sp类是一个模板类,它的目标类型可以是任何类,也就是说可以是LightRefBase类的子类,当然也可以是Re

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值