Android-深入理解常见类
Posted 天津 唐秙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,降低了创建和使用带有消息队列的线程的难度。
以上是关于Android-深入理解常见类的主要内容,如果未能解决你的问题,请参考以下文章