Android-深入理解常见类

深入理解常见类

第一部分 RefBase、sp和wp

RefBase是Android中所有对象的始祖,RefBase结合sp和wp,实现了一套通过引用计数的方式来控制对象生命周期的机制,sp是strong pointer,wp是weak pointer。

1. 初识影子对象

class A : public RefBase
{}

int main()
{
	A* pA = new A;
	{
		sp<A> spA(pA);
		wp<A> wpA(spA>;
	}
}
1.1 RefBase和它的影子

  类A从RefBase中派生,使用的是RefBase构造函数,代码如下:

RefBase::RefBase() : mRefs(new weakref_impl(this))
{
	//影子
}

  mRefs是引用计数管理的关键类,它是从RefBase的内部类weakref_type中派生出来的

class RefBase::weakref_impl : public Refbase::weakref_type

  C++的内部类和java的内部类相似,但它需要一个显示的成员指向外部类,而Java的内部类对象有一个隐式的成员指向外部类对象,内部类在C++中的学名叫做nested class(内嵌类)

weakref_imp1(RefBase* base)
	: mStrong(INITIAL_STRONG_VALUE)
	, mWeak(0)
	, mBase(base)
	, mFlags(0)
	, mStrongRefs(NULL)
	, mWeakRefs(NULL)
	, mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
	, mRetain(false)
	{
	
	}

  new了一个A对象之后,其实我们还new了一个weakref_impl对象,这里称之为影子对象,A对象为实际对象。

1.2 sp上场
sp<A> spA(pA)

  sp是一个模板类

template<typename T>
sp<T>::sp(T* other) //这里的other就是刚才创建的pA
	: m_ptr(other) //sp保存了pA的指针
	{
		if(other) other->incStrong(this); //调用pA的incStrong
	}

  RefBase中的incStrong

void RefBase::incStrong(const void* id) const
{
	//mRefs就是刚才在RefBase构造函数中的new出来的影子对象
	weakref_impl* const refs = mRefs;

	//操作影子对象,先增加弱指针的引用计数
	refs->addWeakRef(id);
	refs->incWeak(id);

	refs->addStrongRef(id);
	const int32_t c = android_atomic_inc(&refs->mStrong);
	if(c != INITIAL_STRONG_VALUE)
	{
		//如果c不是初始值,说明这个对象已经被强引用过了
		return;
	}
	//下面这个是原子加操作,相当于执行refs->mStrong + (-0x10000000),最终mStrong=1
	android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
	const_cast<RefBase*>(this)->onFirstRef();
	}

  addWeakRef啥都没做,因为这个是release版走的分支,一共不用考虑的几个函数,如下:

void addStrongRef(const void* id){}
void removeStrongRef(const void* id){}
void addWeakRef(const void* id){}
void removeWeakRef(const void* id){}
void printfRefs() const {}
void trackMe(bool, bool){}

  incWeak函数代码如下:

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,看上面的代码,android_atomic_xxx是Android平台提供的原子操作函数,原子操作函数是多线程编程中常见的函数。
  sp构造完后,RefBase中影子对象的强引用计数变为1,弱引用计数也变为1,sp的出生导致影子对象的强引用计数加1,弱引用计数也加1。
  wp有好几个构造函数,原理都是一样的:

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::weakref_type* RefBase::createWeak(const void* id) const
{
	//调用影子对象的incWeak,会导致影子对象的弱引用计数增加1
	mRefs->incWeak(id);
	return mRefs; // 返回影子对象本身
}

  我们可以看到,wp化后,影子对象的弱引用计数将增加1,所以现在弱引用计数为2,而强引用计数仍为1,另外,wp中有两个成员变量,一个保存实际对象的,一个保存影子对象,sp只有一个成员变量,用来保存实际对象,但这个实际对象内部已包含了对应的影子对象,wp创建完,现在开始对wp进行析构
  wp进入析构函数,则表明它快离世了,代码如下所示:

template<typename T>
wp<T>::~wp()
{
	if(m_ptr) m_refs->decWeak(this); // 调用影子对象的decWeak,由影子对象的基类实现
}

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;
		}
	}
}

  wp析构后,弱引用计数减1,但由于此时强引用计数和弱引用计数仍为1,所以没有对象被干掉,即没有释放实际对象和影子对象占据的内存。

1.3 sp析构的影响
template<typename T>
sp<T>::~sp()
{
	if(m_ptr) m_ptr->decStrong(this);//调用实际对象的decStrong,由RefBase实现
}

template<typename T>
sp<T>::~sp()
{
	if(m_ptr) m_ptr->decStrong(this);//调用实际对象的decStrong,由RefBase实现
}

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)
	{
		const_cast<RefBase*>(this)->onLastWeakRef(id);
		if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK)
			delete this;
	}
	//......
}

  先看delete this的处理,它会导致A的析构函数被调用,再看A的析构函数:

RefBase::~RefBase()
{
	if(mRefs->mWeak == 0)
		delete mRefs;
}

  RefBase的delete this自杀行为没有把影子对象干掉,我们还在decStrong中:

在这里插入图片描述

在这里插入图片描述

1.4 总结

  1.RefBase中有一个隐含的影子对象,该影子对象内部有强弱引用计数
  2.sp化后,强弱引用计数各增加1,sp析构后,强弱引用计数各减1
  3.wp化后,弱引用计数增加1,wp析构后,弱引用计数减1
  4.完全彻底的消灭RefBase对象,包括让实际对象和影子对象灭亡,这些都是由强弱引用计数控制的,另外还要考虑flag的取值情况,当flag为0时,强引用为0将导致实际对象被delete,弱引用为0将导致影子对象被delete

2. 由弱生强

int main()
{
	A *pA = new A();
	wp<A> wpA(pA);
	sp<A> spA = wpA.promote();//通过promote函数,得到一个sp
}

  对于A的wp化,按照之前的知识点,wp化后仅会使弱引用计数加1,此时,影子对象的弱引用计数为1,强引用计数仍然是初始值0x10000000
  wpA的promote函数是从一个弱对象产生一个强对象的重要函数,代码如下:

template<typename T>
sp<T> wp<T>::promote() const
{
	return sp<T>(m_ptr, m_refs);//调用sp的构造函数
}

template<typename T>
sp<T>::sp(T* p, weakref_type* refs)
	: m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)
	{
		//上面的代码够简洁,不够清楚,写成这样
		T* pTemp = NULL;
		if(p != NULL && refs->attemptIncStrong(this) == true)
			pTemp = p;
		m_ptr = pTemp;
	}

  由弱生强的关键函数是attemptIncStrong,代码如下:
在这里插入图片描述
在这里插入图片描述

2. 1 总结

  promote完成后,相当于增加了一个强引用,根据上面的知识可知,由弱生强成功后,强弱引用计数均增加1,所以现在影子对象的强引用计数为1,弱引用对象计数为2。

3. 破解生死魔咒

3.1 延长生命的魔咒

  RefBase为我们提供了这样的一个函数:

extendObjectLifetime(int32_t mode)
//另外还定义了一个枚举
enmu
{
	OBJECT_LIFETIME_WEAK = 0x0001,
	OBJECT_LIFETIME_FOREVER = 0x0003
};

  注意:FOREVER的值是3,用二进制表示是B11,而WEAK的二进制是B01,也就是说FOREVER包括了WEAK的情况。
  上面这两个枚举值是破除强弱引用计数作用的魔咒,先观察flags为OBJECT_LIFETIME_WEAK的情况:

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,代码如下:
在这里插入图片描述
  然后调用影子对象的decWeak,代码如下:

void RefBase::weakref_type::decWeak(const void* id)
{
	weakref_impl* const impl = static_cast<weakref_impl*>(this);
	impl->removeWeakRef(id);
	if(c != 1)
		return;//c为2,弱引用计数为1,直接返回
	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;
	}
}
3.2 LIFETIME_WEAK的魔力

  在LIFETIME_WEAK下,强引用计数为0,而弱引用计数不为0的时候,实际对象没有被delete,只有当强引用计数和弱引用计数同时为0时,实际对象和影子对象才会被delete。

3.3 LIFETIME_FOREVER

  1.flags为0,强引用计数控制实际对象的生命周期,弱引用计数控制影子对象的生命周期,强引用计数为0,实际对象被delete,所以对于这种情况,应记住的是,使用wp时要由弱生强,以免收到segment fault信号
  2.flags为LIFETIME_WEAK,强引用计数为0,弱引用计数不为0时,实际对象不会被delete,当弱引用计数减为0时,实际对象和影子对象会同时被delete,这是功德圆满的情况。
  3.flags为LIFETIME_FOREVER会彻底摆脱强弱引用计数的控制

4. 轻量级的引用计数控制类LightRefBase

  上面说的RefBase是一个重量级的引用计数控制类,Android为我们提供了一个轻量级的LightRefBase。

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的时候减一,当引用计数变为0的时候,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类是一个模板类,我们可以通过别的类继承LightRefBase来使用。

第二部分 Thread类及常用同步类分析

  Thread类是Android为线程操作而做的一个封装,代码在Thread.cpp中,还封装了一些线程同步相关的类。
  在Thread的构造函数中有一个canCallJava,表示了这个线程是否会使用到JNI函数,Thread类真实的线程创建是在run函数中的。

Thread(bool canCallJava = true);

Thread.cpp

status 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,但是它们调用的函数却不同。
在这里插入图片描述
  其中在第四章第二点中介绍的AndroidRuntime调用的startReg的地方,就可能修改这个函数指针
在这里插入图片描述
  如果mCanCallJava为true,则将调用javaCreateThreadEtc函数:

在这里插入图片描述在这里插入图片描述
mCanCallJava为true的目的:
  1.在调用你的线程函数之前会attach到JNI环境中,这样线程函数就可以调用JNI函数了。
  2.线程函数退出后,它会从JNI环境中detach,释放一些资源
注意:
  在进程退出前,dalvik虚拟机会检查是否有attach,如果最后有未detach的线程,则会直接abort,如果关闭JNI check选项,就不会做这个检查,如果直接使用POSIX的线程创建函数,那么凡是使用过attach的,最后都要detach。

1. 线程函数_threadLoop介绍

在这里插入图片描述
在这里插入图片描述
  threadLoop运行在一个循环中,它的返回值可以决定是否退出线程。

2. 常用同步类

  Android提供了两个封装好的同步类,它们是Mutex和Condition,另外OS还提供了简单的原子操作

2.1 互斥类——Mutex

  Mutex是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源,Mutex的实现方式:
在这里插入图片描述
  Mutex中除了初始化,还有最重要的lock和unlock函数,还提供了一个trylock函数,该函数只是尝试去锁住该区域,使用者需要根据trylock的返回值来判断是否成功锁住了该区域。

AutoLock介绍
  AutoLock是定义在Mutex内部的一个类,这充分利用了C++构造和析构:
在这里插入图片描述
  先定义一个Mutex,如Mutex xlock,在使用xlock的地方定义一个AutoLock,如AutoLock autoLock(xlock),因为C++中对象的构造和析构都是自动被调用,所以在AutoLock的生命周期内,析构函数也就调用了。

2.2 条件类——Condition

  线程A在初始化工作而线程B、C必须要等到初始化工作完成之后才能工作,当线程A完成初始化工作时,会触发这个条件,等待着B、C就会被唤醒。
在这里插入图片描述
在这里插入图片描述
  Condition类必须配合Mutex来实现,在上面的代码中,不论时wait,waitRelative,signal,broadcast的调用,都是放在一个Mutex的lock和unlock范围中,尤其时wait和waitRelative函数的调用。

2.3 原子操作函数介绍

  原子操作函数绝不会在执行完毕前被任何其他任务或者事件打断,原子操作时最小的执行单位。
在这里插入图片描述
  这里使用Mutex是因为g_flag++和g_flag–都不是原子操作,从汇编指令的角度来看,它生成了三条汇编指令:
  1.从内存中取数据到寄存器
  2.对寄存器中的数据进行递增操作,结果还在寄存器中
  3.寄存器的结果写回到内存

  一般情况下,处理这种问题可以使用Mutex来加锁保护,但Mutex的使用方法比它所保护的内容还要复杂,例如,锁的使用将导致从用户态转为内核态,有较大的浪费。X86上,可以以下汇编实现:
在这里插入图片描述
在这里插入图片描述
  原子操作的最大好处在于避免了锁的使用,这对程序的运行效率提高有很大帮助,目前,在多核并行编程中,最高境界就是完全不使用锁。

第三部分 Looper和Handler类分析

  Android系统中Java的应用程序和其他系统上相同,都是靠消息驱动来工作,工作原理:
  1.有一个消息队列,可以往这个消息队列中投递消息
  2.有一个消息循环,不断从消息队列中取出消息,然后处理
在这里插入图片描述
  在Android系统中,这些工作主要由Looper和Handler来实现:
  1.Looper类用于封装消息循环,并且有一个消息队列
  2.Handler类,封装了消息投递、消息处理等接口

1. Looper类分析

在这里插入图片描述
①调用Looper的prepare函数
在这里插入图片描述
  ThreadLocal是Java中的线程局部变量类,它的实现和操作系统提供的线程本地存储有关,该类有两个关键函数:
  set:设置调用线程的局部变量
  get:获取调用线程的局部变量
  prepare会在调用线程的局部变量中设置一个Looper对象,这个调用线程就是LooperThread的run线程,Looper对象构造如下:
在这里插入图片描述
  在调用prepare的线程中,设置了一个Looper对象,这个Looper对象就保存在这个调用线程里TLV中,而Looper对象内部封装了一个消息队列,也就是说,prepare函数通过ThreadLocal机制,把Looper和调用线程关联在了一起。

②Looper循环
在这里插入图片描述
Looper的作用:
  1.封装了一个消息队列
  2.Looper的prepare函数把这个Looper和调用prepare的线程绑定在了一起
  3.处理线程调用loop函数,处理来自该消息队列的消息
当事件源向这个Looper发送消息的时候,其实是把消息加到这个Looper的消息队列中,该消息就将由和Looper绑定的处理线程来处理。

Looper、Message和Handler的关系
  1.Looper中有一个Message队列,里面存储的是一个个待处理的Message。
  2.Message中有一个Handler,这个Handler是用来处理Message的。

2. Handler分析

Handler中的成员:
在这里插入图片描述
  Handler一共有四个构造函数,区别在于对上面三个重要成员变量的初始化上
在这里插入图片描述
  怎么往Looper的消息队列中插入消息?
在这里插入图片描述
在这里插入图片描述
  Handler把Message的target设为自己,是因为Handler除了封装消息添加等功能外还封装了消息处理的接口。我们往Looper的消息队列中加入了一个消息,按照Looper的处理规则,它在获取消息后会调用target的dispatchMessage函数,再把这个消息派发给Handler处理。
在这里插入图片描述
dispatchMessage定义的消息处理的优先级机制
  1.Message如果自带了callback处理,则交给callback处理
  2.Handler如果设置了全局的mCallback,则交给mCallback处理
  3.如果上述都无,消息交给Handler子类实现的handleMassage来处理,当然,这需要从Handler派生并重载handleMessage函数

3. Looper和Handler的同步关系

在这里插入图片描述
  线程1中创建线程2,线程2通过Looper处理消息,线程1得到线程2的Looper,并且根据这个Looper创建一个Handler,这样发送给该Handler的消息将由线程2处理,但是这个代码有着严重的问题,myLooper的创建是在线程2中,而looper的赋值在线程1中,很有可能此时线程2的run函数还没有来的及给myLooper赋值,这样线程1中的looper将取到myLooper的初值null,并且下面这个不能替换:
在这里插入图片描述
  这是因为myLooper返回的是调用线程的Looper,即Thread1的Looper,而不是我们想要的Thread2的Looper,Android提供了一个HandlerThread来解决问题:
在这里插入图片描述
  wait/notifyAll就解决了问题

第四部分 总结

  分析了Android代码中常见的类,Native层包括对象生命周期相关的RefBase、sp、wp、LightRefBase类,以及多线程提供的Thread类和相关的同步类、Java层的Handler类和Looper类,分析了HandlerThread,降低了创建和使用带有消息队列的线程的难度。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天津 唐秙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值