Android 13(T) Media框架 - 智能指针

news2025/2/9 11:46:35

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/640396.html

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

相关文章

前端加载超大图片(100M以上)实现秒开解决方案

前端加载超大图片(100M以上)实现秒开解决方案 前言 前端加载超大图片时&#xff0c;一般可以采取以下措施实现加速&#xff1a; 图片压缩&#xff1a;将图片进行压缩可以大幅减小图片的大小&#xff0c;从而缩短加载时间。压缩图片时需要注意保持图片质量&#xff0c;以免影响…

CSS基础学习--9 边框(Border)

一、CSS 边框属性 CSS边框属性允许你指定一个元素边框的样式和颜色。 二、边框的样式 边框样式属性指定要显示什么样的边界。 border-style属性用来定义边框的样式 border-style 值: <!DOCTYPE html> <html> <head> <meta charset"utf-8">…

【爬虫】4.5 实践项目——爬取当当网站图书数据

目录 1. 网站图书数据分析 2. 网站图书数据提取 3. 网站图书数据爬取 &#xff08;1&#xff09;创建 MySQL 数据库 &#xff08;2&#xff09;创建 scrapy 项目 &#xff08;3&#xff09;编写 items.py 中的数据项目类 &#xff08;4&#xff09;编写 pipelines_1.py …

一文走进 SQL 编译-语义解析

一、概述 SQL 引擎主要由三大部分构成&#xff1a;解析器、优化器和执行器。解析器的主要作用是将客户端传来的命令解析编译成数据库能识别运行的命令&#xff0c;其主要由词法解析、语法解析和语义解析三部分构成&#xff0c;如下图所示。 本文将重点介绍 KaiwuDB 语义解析部…

机器学习-11 BP神经网络

BP神经网络 神经网络介绍前馈神经网络BP神经网络BP神经网络的核心思想误差反向传播算法BP网络结构 反馈神经网络自组织神经网络 神经网络相关概念激活函数Sigmoid函数tanh双曲正切函数ReLU函数Leaky RuLU函数Maxout函数激活函数的选择 损失函数Softmax交叉熵均方差损失函数自定…

chatgpt赋能python:Python文件处理入门指南-如何将Python程序转化为文件

Python文件处理入门指南 - 如何将Python程序转化为文件 Python是一门广泛应用于机器学习、数据分析、网络编程等领域的高级编程语言。Python代码简洁易懂&#xff0c;具有良好的可移植性和跨平台性&#xff0c;因此备受程序员们的喜欢。然而&#xff0c;要想让代码得到更广泛的…

kotlin协程flow retry功能函数返回失败后重试(4)

kotlin协程flow retry功能函数返回失败后重试&#xff08;4&#xff09; import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlockingfun main(args: Array<String>) {var count 0 //重试计数runBlocking {load().onEach…

chatgpt赋能python:Python怎么往表格里写数据

Python怎么往表格里写数据 在Python中&#xff0c;我们经常需要往表格里写入数据。表格是一种最基本的数据储存结构&#xff0c;而Python在处理表格数据方面非常出色。在这篇文章中&#xff0c;我们将介绍Python中常用的几种写入表格的方法。 方法一&#xff1a;使用CSV模块 …

MySQL-索引详解(二)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

哨兵架构redisCluster-Redis(五)

上篇文章介绍了主从架构以及lua脚本。 主从架构&lua脚本-Redis&#xff08;四&#xff09;https://blog.csdn.net/ke1ying/article/details/131159229 Sentinel集群 主从的搭建我们已经完成&#xff0c;但如果主节点宕机&#xff0c;这时候导致整个redis服务不可用怎么办…

Cesium入门之十:Cesium加载3DTiles数据

目录 3DTiles介绍3DTiles数据结构Cesium中与3DTiles相关的类1.Cesium3DTileset类常用属性&#xff1a;常用方法&#xff1a; 2.Cesium3DTileStyle类常用属性&#xff1a; 3.Cesium3DTileContent类常用属性常用方法 4. Cesium3DTileFeature类常用属性常用方法 5.Cesium3DTile类常…

chatgpt赋能python:Python怎么循环

Python怎么循环 循环是编程中最重要的控制结构之一&#xff0c;它允许我们重复执行一组语句&#xff0c;直到满足某个条件为止。在Python中&#xff0c;我们有多种循环结构可供使用&#xff0c;本文将介绍它们及其用法。 for循环 for循环通常用于迭代&#xff08;遍历&#…

chatgpt赋能python:在Python中用何种方式来建立SEO友好网站?

在Python中用何种方式来建立SEO友好网站&#xff1f; 在当今数字时代&#xff0c;一个强大且易于维护的网站是任何企业或组织成功的关键。但是&#xff0c;一个网站的外观和功能不代表它的成功。如果语义不清、标记不恰当或结构不正确&#xff0c;网络爬虫可能会忽略您的网站&…

51单片机“密码锁”代码详解

注&#xff1a;此代码一经过验证&#xff0c;读者不必怀疑其正确性&#xff0c;如果烧录进去没有反应&#xff0c;请自行检查引脚端口配置&#xff0c;以及仔细分析代码实现原理。倘若能静下心来分析代码&#xff0c;一定能受益匪浅。 废话不多说&#xff0c;&#xff0c;直接…

深入理解 SpringBoot 日志框架:从入门到高级应用——(一)日志框架原理

文章目录 了解日志框架常见日志框架面向 SLF4J 编程SLF4J 接口规范其他框架统一转换为 SLF4J 框架 了解日志框架 日志框架的历史可以追溯到计算机编程的早期。在早期的编程语言中&#xff0c;如 C 和 Pascal&#xff0c;程序员通常使用 printf 或 fprintf 函数将程序的状态信息…

总结898

今天在B站上看英文短视频&#xff0c;认识了一位著名的心理学家乔丹彼得森&#xff08;号称“龙虾教授”&#xff09;。他的思想对我 产生了一定的影响。 曾在《写作:自我精进的武器》中看到过写作的5大好处&#xff0c;但他没有乔丹彼得森所讲的那么令我震撼&#xff0c;他对写…

Django框架-1

框架介绍 框架是整个或部分系统的可重用设计&#xff0c;表现为一组抽象构件及构件实例间交互的方法&#xff1b; 框架是可被应用开发者定制的应用骨架&#xff0c;是某种半成品&#xff1b; 使用框架开发的好处 开发周期短维护成本低软件生产效率和质量得到提高 Django框…

面向对象、封装、就近原则及this关键字

面向:拿、找&#xff1b; 对象:能干活的东西&#xff1b; 面向对象编程:拿东西过来做对应的事&#xff1b; 即&#xff0c;分别找对应的“对象”完成对应的“事件”。因此学习内容包括&#xff1a; ①学习各种已存在的对象并使用&#xff1b; ②学习设计对象并使用。 面向对象…

DAY21:二叉树(十一)二叉搜索树中的搜索+验证二叉搜索树(坑比较多,复盘)

文章目录 700.二叉搜索树中的搜索二叉搜索树概念二叉搜索树的搜索方式补充总结 思路递归法迭代法注意这里写if-if和if-else if的区别为什么if-if会访问空的root&#xff1f;if-if结构和if-else if-else的区别 迭代法修改结果&#xff1a; 98.验证二叉搜索树&#xff08;坑比较多…

Java小知识

一、lambda ()->{} ()中为接口唯一方法中的参数&#xff0c;可以任意取 {}为接口唯一方法中的执行语句&#xff0c;返回的结果类型必须符合接口中方法返回值的定义 原理理解&#xff1a; Public interface Printable{ String print(String suffix);} 在函数式编程中有一个方…