Android 13(T) - 智能指针

news2024/10/7 8:23:24

Android有一套自己的智能指针管理办法,并且将其运用在源码的各个角落,所以学习Media框架之前,我们有必要先了解下Android智能指针。
本节代码源自于Android 13(T),参考 (aospxref.com)

1 概述

与智能指针相关的总共有5个类,所以并不会很复杂,小白感觉有机会能看懂!
1.1 智能指针相关的类
RefBase.h一开始有一大段注释,我挑了一些进行了翻译:

  1. 一般来说,想要让指针可以使用sp<>和wp<>来管理,需要让指针类型继承于RefBase原型;
  2. 继承自RefBase的对象只有在引用减为0时才会被释放,不能通过其他方法被释放,所以这些对象不能在栈上创建,或者是直接将数据成员直接暴露给其他对象;
  3. 使用方法上,在一个相互包含的关系里推荐使用sp<>,没有相互包含的关系里用wp<>;两个对象不能同时拥有对方的sp<>成员,这将造成循环引用,导致内存无法被正确释放;
  4. wp<>可以看作是安全的指针,当对象被释放后promote方法会返回nullptr,这样可以避免空指针;
  5. 一般来说,当sp<>销毁后,其保存的对象也将销毁,但是有一部分相关的内存需要等到最后一个wp<>被销毁才会释放;extendObjectLifetime方法可以用来阻止“强引用计数为0而弱引用计数不为0”这种情况下,对象被释放;

可以看出智能指针的使用需要有很多注意点,了解智能指针,android源码才能理解的更加透彻。接下来就开始边学习边理解上述注意点的内容吧!


2 相关类型定义

2.1 引用计数

和std::shared_ptr相同,android智能指针也采用引用计数来管理堆对象的自动释放。引用计数简单来说就是记录有多少使用者在使用同一个对象,每多一个使用者计数加一,每少一个计数减一,当计数为0时就释放该对象

Android为我们提供引用计数管理的类为weakref_type和其子类weakref_impl,定义如下:

class RefBase::weakref_impl : public RefBase::weakref_type
{
public:
	std::atomic<int32_t>    mStrong;
	std::atomic<int32_t>    mWeak;
	RefBase* const          mBase;
	std::atomic<int32_t>    mFlags;
	explicit weakref_impl(RefBase* base)
		: mStrong(INITIAL_STRONG_VALUE)
		, mWeak(0)
		, mBase(base)
		, mFlags(OBJECT_LIFETIME_STRONG){
	}
}

class weakref_type
{
public:
	RefBase*            refBase() const;
	void                incWeak(const void* id);
	void                incWeakRequireWeak(const void* id);
	void                decWeak(const void* id);
	bool                attemptIncStrong(const void* id);
	bool                attemptIncWeak(const void* id);
	int32_t             getWeakCount() const;
}

weakref_impl使用mStrong用于强引用计数,mWeak用于弱引用计数,mBase记录指针,mFlags作为标志,其他方法为空实现。RefBase通过直接访问weakref_impl的计数成员来操作强引用计数,操作弱引用计数则需要通过调用其父类的方法来完成。


2.2 RefBase

RefBase类持有以上引用计数对象,并向外提供修改引用计数的接口。

class RefBase
{
public:
	void            incStrong(const void* id) const;
	void            incStrongRequireStrong(const void* id) const;
	void            decStrong(const void* id) const;	
	void            forceIncStrong(const void* id) const;
	int32_t         getStrongCount() const;
    weakref_type*   createWeak(const void* id) const;       
	weakref_type*   getWeakRefs() const;
private:
	weakref_impl* const mRefs;

实例化RefBase时会创建一个weakref_impl,并将RefBase本身传给weakref_impl

RefBase::RefBase()
    : mRefs(new weakref_impl(this)){ }

RefBase提供有三个增加强引用计数的方法:incStrong是在第一次创建RefBase对象时使用、incStrongRequireStrong是在有其他引用时调用,forceIncStrong似乎使用的比较少。

以RefBase::incStrong()方法为例,先调用weakref_type的incWeak方法来增加一个弱引用计数mWeak,然后再直接修改mStrong增加强引用计数。

void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id);
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }
    int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
    refs->mBase->onFirstRef();
}

void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
}

我们可以观察到,强引用计数加一,弱引用计数就会对应加一,那么什么时候两种计数方法会出现不同呢?这里先留下一个疑问。

减少强引用计数只有一个接口decStrong,以下是简化的代码:

void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
    if (c == 1) {
        std::atomic_thread_fence(std::memory_order_acquire);
        refs->mBase->onLastStrongRef(id);
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            delete this;
        }
    }
    refs->decWeak(id);
}

RefBase::~RefBase()
{
    int32_t flags = mRefs->mFlags.load(std::memory_order_relaxed);
    if ((flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {
        if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) {
            delete mRefs;
        }
    } else if (mRefs->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
    }
    const_cast<weakref_impl*&>(mRefs) = nullptr;
}

当强引用计数为0时,将会去检查mFlag的值,如果是OBJECT_LIFETIME_STRONG,那么就释放当前对象,这边要注意的是析构函数中并没有释放mRefs。释放完对象之后还会将弱引用计数减一,当弱引用计数为0时,mRefs被释放。

void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    if (c != 1) return;
    atomic_thread_fence(std::memory_order_acquire);
    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
        if (impl->mStrong.load(std::memory_order_relaxed)
                == INITIAL_STRONG_VALUE) {
        } else {
            delete impl;
        }
    } else {
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}

mFlag为OBJECT_LIFETIME_WEAK的情况下,如果强引用计数减为0,不会立即释放当前对象,需要等到弱引用计数为0,才会释放对象,释放对象的过程中会同步释放mRefs。

RefBase暂时就先了解这么多,最后有一点要注意的是,RefBase的构造函数和析构函数的访问权限均为protected,也就是说我们不能直接创建RefBase对象,而是要通过子类访问使用。


2.3 sp<>

sp即StrongPinter,可以帮我们完成强指针引用计数的管理。创建对象或者有新增对象引用时自动帮我们执行incStrong:

template<typename T>
sp<T>::sp(const sp<T>& other)
        : m_ptr(other.m_ptr) {
    if (m_ptr)
        m_ptr->incStrong(this);
}

对象使用完成时自动执行decStrong:

template<typename T>
sp<T>::~sp() {
    if (m_ptr)
        m_ptr->decStrong(this);
}

由于智能指针是用于管理堆上创建的对象的,所以sp还会帮我们检查创建的对象是否在堆上。以MediaCodec为例,析构函数访问权限为protected,我们则不能创建栈上的对象,堆上的对象也不能用delete来释放,这样只能使用智能指针来管理,变得更加安全。

struct MediaCodec : public AHandler {
protected:
	virtual ~MediaCodec();
}

2.4 wp<>

一起来看看以下代码是否存在问题:

class B;
class A : public RefBase
{
public:
	A() { std::cout << "create A"<< std::endl; }
	void setPointer(sp<B> B) { p = B; }
protected:
	~A() { std::cout << "delete A" << std::endl; }
private:
	sp<B> p;
};

class B : public RefBase
{
public:
	B() { std::cout << "create B " << std::endl; }
	void setPointer(sp<A> A) { p = A; }
protected:
	~B() { std::cout << "delete B "<< std::endl; }
private:
	sp<A> p;
};

int main() {
	{
		sp<A> a(new A);
		sp<B> b(new B);
		printf("%d %d\n", a.getStrongCount(), b.getStrongCount());
		a->setPointer(b);
		b->setPointer(a);
		printf("%d %d\n", a.getStrongCount(), b.getStrongCount());
	}
	/*
	create A
	create B
	1 1
	2 2
	*/
}

可以看到代码执行结束时并没有打印delete A 和 delete B,A和B的内存没有被正确释放。b先出作用域,此时b的引用计数从二变成一,因此b不会被释放,b中存储的a也没有被释放;a后出作用域,a的引用计数从二变成一,因此a也没有被释放。以上代码被称为循环引用,会造成内存泄漏。Android提供了wp来解决循环引用造成的问题。

wp即WeakPointer,创建wp对象时只会让弱引用计数加一,强引用计数并不会增加,这时候强弱引用计数就不相同了(这也就是上面疑问的答案)。由于强引用计数没有加一,因此也就不会形成循环引用。

template<typename T>
wp<T>::wp(const sp<T>& other)
    : m_ptr(other.m_ptr)
{
    m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
}

RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    mRefs->incWeak(id);
    return mRefs;
}

void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
}

wp可以避免出现循环引用,那我们要如何使用wp呢?

下面是wp的声明,由于没有重载*->,所以并不能直接操作内部的指针成员,只有通过promote方法获得一个sp来间接使用内部指针。

class wp
{
public:
	sp<T> promote() const;
private:
    T*              m_ptr;
    weakref_type*   m_refs;	
}	

promote会调用attemptIncStrong尝试获取强指针:

  • 强引用计数不为0,那么直接返回一个sp对象,强引用计数加一
  • 强引用计数小于等于0,或者为初始值
    • flag为OBJECT_LIFETIME_STRONG,计数小于等于0则无法返回sp,计数为初始值(说明创建对象是时以wp存储)则返回一个sp,强引用计数加一
    • flag为OBJECT_LIFETIME_WEAK,强引用计数加一,返回一个sp

我们可以对promote的返回结果判空,避免空指针的使用。

template<typename T>
sp<T> wp<T>::promote() const
{
    sp<T> result;
    if (m_ptr && m_refs->attemptIncStrong(&result)) {
        result.set_pointer(m_ptr);
    }
    return result;
}

bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
	incWeak(id);
	weakref_impl* const impl = static_cast<weakref_impl*>(this);
	int32_t curCount = impl->mStrong.load(std::memory_order_relaxed);
	
	while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
	    if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
	            std::memory_order_relaxed)) {
	        break;
    }
	if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
		int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            if (curCount <= 0) {
                decWeak(id);
                return false;
            }
            while (curCount > 0) {
                if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
                        std::memory_order_relaxed)) {
                    break;
                }
            }
        } else {
            curCount = impl->mStrong.fetch_add(1, std::memory_order_relaxed);
            if (curCount != 0 && curCount != INITIAL_STRONG_VALUE) {
                impl->mBase->onLastStrongRef(id);
            }
        }
    }

    if (curCount == INITIAL_STRONG_VALUE) {
        impl->mStrong.fetch_sub(INITIAL_STRONG_VALUE,
                std::memory_order_relaxed);
    }
    return true;    	
}

RefBase.h中已有说明,在一个相互包含的关系里推荐使用sp,没有相互包含的关系里用wp。这里以AHandlerALooper举个例子:ALooper中注册有AHandler对象,这里有包含关系所以使用spAHandler虽然持有ALooper,但是没有包含关系,所以使用wp

struct AHandler : public RefBase {
private:
	wp<ALooper> mLooper;
}

struct ALooper : public RefBase {
	handler_id registerHandler(const sp<AHandler> &handler);
}

以上就是我理解android智能指针的思路,到这里再看源码中形形色色的spwp,就知道他们是在传递什么,如何传递了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/652071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某小厂面试加答案(6.15)

看 Java 面试题就去 www.javacn.site 磊哥新推出《企业面经和答案》栏目&#xff0c;最近会持续更新&#xff0c;欢迎大家订阅此账号查看&#xff0c;或访问 www.javacn.site 查看。 面经来源于牛客&#xff0c;如下图所示&#xff1a; https://www.nowcoder.com/feed/main/det…

OpenAI的创始人World Coin项目介绍

&#x1f3af; 在一个崇高的目标支持下&#xff0c;不停地工作&#xff0c;即使慢&#xff0c;也一定会获得成功。—— 爱因斯坦 如果你对项目感兴趣请联系v&#xff1a;weixin605405145 一、项目速览 项目背景 Worldcoin由OpenAI的创始人Sam Altman于2019年创立&#xff0c;就…

【C++】的继承

继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构…

010、体系架构之TiFlash

TiFlash TiFlash 功能架构异步复制一致性读取场景选择是选择TiKV还是TiFLash TiFlash 功能 异步复制一致性读取(写虽然是异步&#xff0c;但读可以做到一致性)引擎智能选择计算加速 架构 TiFLASH 也是通过raft 算法进行同步&#xff0c;但它不怎么消耗资源&#xff0c;因为它…

ProGuard 进阶系列(二)配置解析

书接上文&#xff0c;从开源库中把代码下载到本地后&#xff0c;就可以在 IDE 中进行运行了。从 main 方法入手&#xff0c;可以看到 ProGuard 执行的第一步就是去解析参数。本文的内容主要分析源码中我们配置的规则解析的实现。 在上一篇文章末尾&#xff0c;在 IDE 中&#x…

Vue Router4

后端路由 客户端请求不同的URL服务器匹配URL并给一个Controller处理Controller处理完返回渲染好的HTML页面或数据给前端 优点&#xff1a; 不需要单独加载js和css&#xff0c;直角交给浏览器展示&#xff0c;有利于SEO优化 缺点&#xff1a; 页面有后端人员编写或由前端人员…

告别里程焦虑:深蓝S7超级增程打造超长续航

提起新能源汽车&#xff0c;估计许多人第一时间都会想要查看它的续航里程。 虽然如今的新能源汽车在续航里程上较过去已经有了很大改进&#xff0c;但是稀缺的充电桩和漫长的充电时间&#xff0c;仍然无法让需要长途出行的用户摆脱里程焦虑。 那么问题就来了&#xff1a;有没有…

基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码+文档

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码文档 视频演示 https://www.bilibili.com/video/BV1HW4y197Fe/ 系统介绍 摘 要 …

dubbo源码之-ExtensionInjector

dubbo源码之-ExtensionInjector 概述源码入口Extension 是如何获取到&#xff1f;SpiExtensionInjector 概述 其实ExtensionInjector 非常简单&#xff0c; 我们知道dubbo有ioc注入的功能&#xff0c; 是靠的set方法注入&#xff0c;对应的底层源码主要是ExtensionInjector 如…

MySQL数据库语言一、DDL

&#x1f618;作者简介&#xff1a;正在努力的99年打工人。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;动…

华为OD机试真题B卷 JavaScript 实现【分班】,附详细解题思路

一、题目描述 幼儿园两个班的小朋友在排队时混在了一起&#xff0c;每位小朋友都知道自己是否与前面一位小朋友是否同班&#xff0c;请你帮忙把同班的小朋友找出来。 小朋友的编号为整数&#xff0c;与前一位小朋友同班用Y表示&#xff0c;不同班用N表示。 二、输入描述 输…

uniapp/手机APP使用支付宝支付(服务端)

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

chatgpt赋能python:Python接口应用于SEO的指南

Python接口应用于SEO的指南 Python成为了web开发中最流行的语言之一&#xff0c;而且尤其在SEO领域中被广泛应用。一些Python库和框架可帮助SEO团队实现其目标&#xff0c;如排名跟踪&#xff0c;爬取数据&#xff0c;进行网站分析&#xff0c;等等。在本文中&#xff0c;我们…

基于Hexo和Butterfly创建个人技术博客,(9) 优化butterfly主题配置文章版本

Butterfly官方网站&#xff0c;请 点击进入 本章目标&#xff1a; 掌握butterfly主题对文章的配置&#xff0c;熟悉并可按需配置到个人的博客站点中&#xff0c;本章内容是一个必会章节&#xff0c;不仅包括文章的UI美化、SEO相关配置还包括其它增加的功能&#xff0c;内容不多…

英语不好能不能学编程?

入门教程、案例源码、学习资料、读者群 请访问&#xff1a; python666.cn 大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 常有人问我&#xff1a;我英语不好&#xff0c;想学编程行不行&#xff1f; 这个问题需要分情况讨论。 1. 可以学 如果你因为担心自己英语不…

chatgpt赋能python:Python怎么用?Python编程的入门指南

Python怎么用&#xff1f;Python编程的入门指南 Python是一种流行的高级编程语言&#xff0c;它被广泛应用于数据分析、机器学习、Web开发、自动化测试等领域。Python语言非常容易学习和使用&#xff0c;因此非常适合初学者和有经验的开发人员。在这篇文章中&#xff0c;我们将…

手把手教你在CentOS7.9上使用docker 安装MySQL5.7

前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文主要讲解如何用docker在centos7.9系统上安装MySQL5.7&#xff0c;以及如何设置MySQL的远程登录。 文章收录到【容器管理】和【数据库入门到精通专栏】&#xff0c;此专栏是沐风晓月对linux云计算架构…

chatgpt赋能python:Python怎么清除动点轨迹?

Python怎么清除动点轨迹&#xff1f; 引言 在数据科学和可视化的领域中&#xff0c;动点轨迹是很有用的工具。动点轨迹可以轻松地显示数据点的时间序列&#xff0c;这可以帮助分析者发现有关数据集的有用信息。然而&#xff0c;当轨迹过于密集和复杂时&#xff0c;这种可视化…

Spring Cloud Alibaba - Sentinel源码分析(一)

目录 一、Sentinel核心源码分析 1、Sentinel核心概念 1.1、Node之间的关系 2、Sentinel源码入口 2.0、Sentinel源码启动 2.1、SlotChain解析 2.2、NodeSelectorSlot解析 2.3、ClusterBuilderSlot解析 2.4、StatisticSlot解析 2.5、FlowSlot解析 2.6、DegradeSlot解析…

白鲸优化算法优化VMD参数,最小包络熵为适应度函数,提取最小包络熵对应的IMF分量,采集最佳IMF分量的9种时域指标,提取特征向量。以西储大学数据为例,附MATLAB代码

大家看到这篇文章&#xff0c;肯定会有疑问&#xff0c;难道本篇文章和上一篇文章不是一个意思嘛&#xff0c;这是来凑数的嘛……其实不然&#xff0c;如果各位读者仔细看&#xff0c;就会发现本篇文章和上一篇文章大有不同&#xff0c;这篇文章也是我一直以来想在上一篇文章基…