本章主要內容 java
· 分析RefBase、sp,wp和LightRefBase類。 android
· 分析Native的Thread類和經常使用同步類。 程序員
· 分析Java層的Handler、Looper,以及HandlerThread類。 編程
本章涉及的源代碼文件名稱及位置 多線程
下面是咱們本章分析的源碼文件名和它的位置。 框架
· RefBase.h 函數
framework/base/include/utils/RefBase.h oop
· RefBase.cpp 學習
framework/base/libs/utils/RefBase.cpp this
· Thread.cpp
framework/base/libs/utils/Thread.cpp
· Thread.h
framework/base/include/utils/Thread.h
· Atomic.h
system/core/include/cutils/Atomic.h
· AndroidRuntime.cpp
framework/base/core/jni/AndroidRuntime.cpp
· Looper.java
framework/base/core/Java/Android/os/Looper.java
· Handler.java
framework/base/core/Java/Android/os/Handler.java
· HandlerThread.java
framework/base/core/Java/Android/os/HandlerThread.java
初次接觸Android源碼,最常見到的必定是sp和wp。若是你只是沉迷於Java世界,那麼Looper和Handler也是避不開的。本章的目的,就是把常常遇見的這些內容中的「攔路虎」一網打盡,將它們完全搞懂。至於弄明白它們有什麼好處,就是仁者見仁,智者見智了。我我的以爲,可能Looper和Handler會相對更實用一些。
RefBase是Android中全部對象的始祖,相似MFC中的CObject及Java中的Object對象。在Android中,RefBase結合sp和wp,實現了一套經過引用計數的方法來控制對象生命週期的機制。就如咱們想像的那樣,這三者的關係很是曖昧。初次接觸Android源碼的人每每會被那個隨處可見的sp和wp搞暈了頭。
什麼是sp和wp呢?其實,sp並非我開始所想的smart pointer(C++語言中有這個東西),它真實的意思應該是strong pointer,而wp是weak pointer的意思。我認爲,Android推出這一套機制多是模仿Java,由於Java世界中有所謂weak reference之類的東西。sp和wp的目的,就是爲了幫助健忘的程序員回收new出來的內存。
我仍是喜歡赤裸裸地管理內存的分配和釋放。不過,目前sp和wp的使用已經深刻到Android系統的各個角落,想把它去掉真是不太可能了。
這三者的關係比較複雜,都說程咬金的「三板斧」很厲害,那麼咱們就借用這三板斧,揭密其相互間的曖昧關係。
咱們的「三板斧」,其實就是三個例子。相信這三板斧劈下去,你會很容易理解它們。
[-->例子1]
//類A從RefBase派生,RefBase是萬物的始祖
class A:public RefBase
{
//A沒有任何本身的功能
}
int main()
{
A* pA =new A;
{
//注意咱們的sp,wp對象是在{}中建立的,下面的代碼先建立sp,而後建立wp
sp<A>spA(A);
wp<A>wpA(spA);
//大括號結束前,先析構wp,再析構sp
}
}
例子夠簡單吧?但也需一步一步分析這斧子是怎麼劈下去的。
類A從RefBase中派生。使用的是RefBase構造函數。代碼以下所示:
[-->RefBase.cpp]
RefBase::RefBase()
:mRefs(new weakref_impl(this))//注意這句話
{
//mRefs是RefBase的成員變量,類型是weakref_impl,咱們暫且叫它影子對象
//因此A有一個影子對象
}
mRefs是引用計數管理的關鍵類,須要進去觀察。它是從RefBase的內部類weakref_type中派生出來的。
先看看它的聲明:
class RefBase::weakref_impl : public RefBase::weakref_type
//從RefBase的內部類weakref_type派生
因爲Android頻繁使用C++內部類的方法,因此初次閱讀Android代碼時可能會有點不太習慣,C++的內部類和Java內部類類似,但不一樣的是,它須要一個顯示的成員指向外部類對象,而Java內部類對象就有一個隱式的成員指向外部類對象。
說明:內部類在C++中的學名叫nested class(內嵌類)。
[-->RefBase.cpp::weakref_imple構造]
weakref_impl(RefBase* base)
:mStrong(INITIAL_STRONG_VALUE) //強引用計數,初始值爲0x1000000
,mWeak(0) //弱引用計數,初始值爲0
,mBase(base)//該影子對象所指向的實際對象
,mFlags(0)
,mStrongRefs(NULL)
,mWeakRefs(NULL)
,mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
,mRetain(false)
{
}
如你所見,new了一個A對象後,其實還new了一個weakref_impl對象,這裏稱它爲影子對象,另外咱們稱A爲實際對象。
這裏有一個問題:影子對象有什麼用?
能夠仔細想一下,是否是發現影子對象成員中有兩個引用計數?一個強引用,一個弱引用。若是知道引用計數和對象生死有些許關聯的話,就容易想到影子對象的做用了。
按上面的分析,在構造一個實際對象的同時,還會悄悄地構造一個影子對象,在嵌入式設備的內存不是很緊俏的今天,這個影子對象的內存佔用已不成問題了。
程序繼續運行,如今到了
sp<A> spA(A);
請看sp的構造函數,它的代碼以下所示:(注意,sp是一個模板類,對此不熟悉的讀者能夠去翻翻書,或者乾脆把全部出現的T都換成A。)
[-->RefBase.h::sp(T*other)]
template<typename T>
sp<T>::sp(T* other) //這裏的other就是剛纔建立的pA
:m_ptr(other)// sp保存了pA的指針
{
if(other) other->incStrong(this);//調用pA的incStrong
}
OK,戰場轉到RefBase的incStrong中。它的代碼以下所示:
[-->RefBase.cpp]
void RefBase::incStrong(const void* id) const
{
//mRefs就是剛纔RefBase構造函數中new出來的影子對象
weakref_impl*const refs = mRefs;
//操做影子對象,先增長弱引用計數
refs->addWeakRef(id);
refs->incWeak(id);
......
先來看看影子對象的這兩個weak函數都幹了些什麼。
先來看第一個函數addWeakRef,代碼以下所示:
[-->RefBase.cpp]
void addWeakRef(const void* /*id*/) { }
呵呵,addWeakRef啥都沒作,由於這是release版走的分支。調試版的代碼咱們就不討論了,它是給創造RefBase、 sp,以及wp的人調試用的。
調試版分支的代碼不少,看來創造它們的人,也爲不理解它們之間的曖昧關係痛苦不已。
總之,一共有這麼幾個不用考慮的函數,咱們都已列出來了。之後再遇見它們,乾脆就直接跳過的是:
void addStrongRef(const void* /*id*/) { }
void removeStrongRef(const void* /*id*/) { }
void addWeakRef(const void* /*id*/) { }
void removeWeakRef(const void* /*id*/) { }
void printRefs() const { }
void trackMe(bool, bool) { }
繼續咱們的征程。再看incWeak函數,代碼以下所示:
[-->RefBase.cpp]
void RefBase::weakref_type::incWeak(const void*id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->addWeakRef(id); //上面說了,非調試版什麼都不幹
const int32_tc = android_atomic_inc(&impl->mWeak);
//原子操做,影子對象的弱引用計數加1
//千萬記住影子對象的強弱引用計數的值,這是完全理解sp和wp的關鍵
}
好,咱們再回到incStrong,繼續看代碼:
[-->RefBase.cpp]
......
//剛纔增長了弱引用計數
//再增長強引用計數
refs->addStrongRef(id);//非調試版這裏什麼都不幹
//下面函數爲原子加1操做,並返回舊值。因此c=0x1000000,而mStrong變爲0x1000001
const int32_t c =android_atomic_inc(&refs->mStrong);
if (c!= INITIAL_STRONG_VALUE) {
//若是c不是初始值,則代表這個對象已經被強引用過一次了
return;
}
//下面這個是原子加操做,至關於執行refs->mStrong +(-0x1000000),最終mStrong=1
android_atomic_add(-INITIAL_STRONG_VALUE,&refs->mStrong);
/*
若是是第一次引用,則調用onFirstRef,這個函數很重要,派生類能夠重載這個函數,完成一些
初始化工做。
*/
const_cast<RefBase*>(this)->onFirstRef();
}
說明:android_atomic_xxx是Android平臺提供的原子操做函數,原子操做函數是多線程編程中的常見函數,讀者能夠學習原子操做函數知識,本章後面將對其作介紹。
sp構造完後,它給這個世界帶來了什麼?
· 那就是RefBase中影子對象的強引用計數變爲1,弱引用計數也變爲1。
更準確的說法是,sp的出生致使影子對象的強引用計數加1,弱引用計數加1。
繼續看wp,例子中的調用方式以下:
wp<A> wpA(spA)
wp有好幾個構造函數,原理都同樣。來看這個最多見的:
[-->RefBase.h::wp(constsp<T>& other)]
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.cpp]
RefBase::weakref_type* RefBase::createWeak(constvoid* id) const
{
//調用影子對象的incWeak,這個咱們剛纔講過了,將致使影子對象的弱引用計數增長1
mRefs->incWeak(id);
returnmRefs; //返回影子對象自己
}
咱們能夠看到,wp化後,影子對象的弱引用計數將增長1,因此如今弱引用計數爲2,而強引用計數仍爲1。另外,wp中有兩個成員變量,一個保存實際對象,另外一個保存影子對象。sp只有一個成員變量用來保存實際對象,但這個實際對象內部已包含了對應的影子對象。
OK,wp建立完了,如今開始進入wp的析構。
wp進入析構函數,這代表它快要離世了。
[-->RefBase.h]
template<typename T>
wp<T>::~wp()
{
if(m_ptr) m_refs->decWeak(this); //調用影子對象的decWeak,由影子對象的基類實現
}
[-->RefBase.cpp]
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
constint32_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;
}
}
}
OK,在例1中,wp析構後,弱引用計數減1。但因爲此時強引用計數和弱引用計數仍爲1,因此沒有對象被幹掉,即沒有釋放實際對象和影子對象佔據的內存。
下面進入sp的析構。
[-->RefBase.h]
template<typename T>
sp<T>::~sp()
{
if(m_ptr) m_ptr->decStrong(this); //調用實際對象的decStrong。由RefBase實現
}
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);//調用影子對象的removeStrongRef,啥都不幹
//注意,此時強弱引用計數都是1,下面函數調用的結果是c=1,強引用計數爲0
constint32_t c = android_atomic_dec(&refs->mStrong);
if (c== 1) { //對於咱們的例子, c爲1
//調用onLastStrongRef,代表強引用計數減爲0,對象有可能被delete
const_cast<RefBase*>(this)->onLastStrongRef(id);
//mFlags爲0,因此會經過delete this把本身幹掉
//注意,此時弱引用計數仍爲1
if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
delete this;
}
......
}
先看delete this的處理,它會致使A的析構函數被調用;再看A的析構函數,代碼以下所示:
[-->例子1::~A()]
//A的析構直接致使進入RefBase的析構。
RefBase::~RefBase()
{
if(mRefs->mWeak == 0) { //弱引用計數不爲0,而是1
delete mRefs;
}
}
RefBase的delete this自殺行爲沒有把影子對象幹掉,但咱們還在decStrong中,可接着從delete this往下看:
[-->RefBase.cpp]
....//接前面的delete this
if ((refs->mFlags&OBJECT_LIFETIME_WEAK)!= OBJECT_LIFETIME_WEAK) {
delete this;
}
//注意,實際數據對象已經被幹掉了,因此mRefs也沒有用了,可是decStrong剛進來
//的時候就保存mRefs到refs了,因此這裏的refs指向影子對象
refs->removeWeakRef(id);
refs->decWeak(id);//調用影子對象decWeak
}
[-->RefBase.cpp]
void RefBase::weakref_type::decWeak(const void*id)
{
weakref_impl*const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);//非調試版不作任何事情
//調用前影子對象的弱引用計數爲1,強引用計數爲0,調用結束後c=1,弱引用計數爲0
constint32_t c = android_atomic_dec(&impl->mWeak);
if (c!= 1) return;
//此次弱引用計數終於變爲0,而且mFlags爲0, mStrong也爲0。
if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
if(impl->mStrong == INITIAL_STRONG_VALUE)
delete impl->mBase;
else {
delete impl; //impl就是this,把影子對象本身幹掉
}
} else{
impl->mBase->onLastWeakRef(id);
if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
delete impl->mBase;
}
}
}
好,第一板斧劈下去了!來看看它的結果是什麼。
第一板斧事後,來總結一下剛纔所學的知識:
· RefBase中有一個隱含的影子對象,該影子對象內部有強弱引用計數。
· sp化後,強弱引用計數各增長1,sp析構後,強弱引用計數各減1。
· wp化後,弱引用計數增長1,wp析構後,弱引用計數減1。
徹底完全地消滅RefBase對象,包括讓實際對象和影子對象滅亡,這些都是由強弱引用計數控制的,另外還要考慮flag的取值狀況。當flag爲0時,可得出以下結論:
· 強引用爲0將致使實際對象被delete。
· 弱引用爲0將致使影子對象被delete。
再看第二個例子,代碼以下所示:
[-->例子2]
int main()
{
A *pA =new A();
wp<A> wpA(A);
sp<A> spA = wpA.promote();//經過promote函數,獲得一個sp。
}
對A的wp化,再也不作分析了。按照前面所學的知識,wp化後僅會使弱引用計數加1,因此此處wp化的結果是:
· 影子對象的弱引用計數爲1,強引用計數仍然是初始值0x1000000。
wpA的promote函數是從一個弱對象產生一個強對象的重要函數,試看:
代碼以下所示:
[-->RefBase.h]
template<typename T>
sp<T> wp<T>::promote() const
{
returnsp<T>(m_ptr, m_refs); //調用sp的構造函數。
}
[-->RefBase.h]
template<typename T>
sp<T>::sp(T* p, weakref_type* refs)
:m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)//有點看不清楚
{
//上面那行代碼夠簡潔,可是不方便閱讀,咱們寫成下面這樣:
/*
T* pTemp= NULL;
//關鍵函數attemptIncStrong
if(p !=NULL && refs->attemptIncStrong(this) == true)
pTemp = p;
m_ptr =pTemp;
*/
}
由弱生強的關鍵函數是attemptIncStrong,它的代碼以下所示:
[-->RefBase.cpp]
boolRefBase::weakref_type::attemptIncStrong(const void* id)
{
incWeak(id);//增長弱引用計數,此時弱引用計數變爲2
weakref_impl* const impl = static_cast<weakref_impl*>(this);
int32_t curCount = impl->mStrong; //這個還是初始值
//下面這個循環,在多線程操做同一個對象時可能會循環屢次。這裏能夠不去管它,
//它的目的就是使強引用計數增長1
while(curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
if(android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {
break;
}
curCount = impl->mStrong;
}
if(curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
bool allow;
/*
下面這個allow的判斷極爲精妙。impl的mBase對象就是實際對象,有可能已經被delete了。
curCount爲0,表示強引用計數確定經歷了INITIAL_STRONG_VALUE->1->...->0的過程。
mFlags就是根據標誌來決定是否繼續進行||或&&後的判斷,由於這些判斷都使用了mBase,
如不作這些判斷,一旦mBase指向已經回收的地址,你就等着segment fault吧!
其實,我們大可沒必要理會這些東西,由於它不影響咱們的分析和理解。
*/
if(curCount == INITIAL_STRONG_VALUE) {
allow =(impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK
|| impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
}else {
allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) ==OBJECT_LIFETIME_WEAK
&& impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG,id);
}
if(!allow) {
//allow爲false,表示不容許由弱生強,弱引用計數要減去1,這是由於我們進來時加過一次
decWeak(id);
return false; //由弱生強失敗
}
//容許由弱生強,則強引用計數要增長1,而弱引用計數已經增長過了
curCount = android_atomic_inc(&impl->mStrong);
if(curCount > 0 && curCount < INITIAL_STRONG_VALUE) {
impl->mBase->onLastStrongRef(id);
}
}
impl->addWeakRef(id);
impl->addStrongRef(id);//兩個函數調用沒有做用
if(curCount == INITIAL_STRONG_VALUE) {
//強引用計數變爲1
android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);
//調用onFirstRef,通知該對象第一次被強引用
impl->mBase->onFirstRef();
}
returntrue; //由弱生強成功
}
promote完成後,至關於增長了一個強引用。根據上面所學的知識可知:
· 由弱生強成功後,強弱引用計數均增長1。因此如今影子對象的強引用計數爲1,弱引用計數爲2。
RefBase爲咱們提供了一個這樣的函數:
extendObjectLifetime(int32_t mode)
另外還定義了一個枚舉:
enum {
OBJECT_LIFETIME_WEAK = 0x0001,
OBJECT_LIFETIME_FOREVER = 0x0003
};
注意:FOREVER的值是3,二進制表示是B11,而WEAK的二進制是B01,也就是說FOREVER包括了WEAK的狀況。
上面這兩個枚舉值,是破除強弱引用計數做用的魔咒。先觀察flags爲OBJECT_LIFETIME_WEAK的狀況,見下面的例子。
[-->例子3]
class A:public RefBase
{
publicA()
{
extendObjectLifetime(OBJECT_LIFETIME_WEAK);//在構造函數中調用
}
}
int main()
{
A *pA =new A();
wp<A> wpA(A);//弱引用計數加1
{
sp<A>spA(pA) //sp後,結果是強引用計數爲1,弱引用計數爲2
}
....
}
sp的析構將直接調用RefBase的decStrong,它的代碼以下所示:
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);
constint32_t c = android_atomic_dec(&refs->mStrong);
if (c== 1) { //上面原子操做後,強引用計數爲0
const_cast<RefBase*>(this)->onLastStrongRef(id);、
//注意這句話。若是flags不是WEAK或FOREVER的話,將delete數據對象
//如今咱們的flags是WEAK,因此不會delete 它
if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
delete this;
}
}
refs->removeWeakRef(id);
refs->decWeak(id);//調用前弱引用計數是2。
}
而後調用影子對象的decWeak。再來看它的處理,代碼以下所示:
[-->RefBase.cpp::weakref_type的decWeak()函數]
void RefBase::weakref_type::decWeak(const void*id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);
constint32_t c = android_atomic_dec(&impl->mWeak);
if (c!= 1) return; //c爲2,弱引用計數爲1,直接返回。
/*
假設咱們如今到了例子中的wp析構之處,這時也會調用decWeak,調用上邊的原子減操做後
c=1,弱引用計數變爲0,此時會繼續往下運行。因爲mFlags爲WEAK ,因此不知足if的條件
*/
if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
if(impl->mStrong == INITIAL_STRONG_VALUE)
delete impl->mBase;
else {
delete impl;
}
} else{//flag爲WEAK,知足else分支的條件
impl->mBase->onLastWeakRef(id);
/*
因爲flags值知足下面這個條件,因此實際對象會被delete,根據前面的分析, 實際對象的delete會檢查影子對象的弱引用計數,若是它爲0,則會把影子對象也delete掉。
因爲影子對象的弱引用計數此時已經爲0,因此影子對象也會被delete。
*/
if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
delete impl->mBase;
}
}
}
看完上面的例子,咱們發現什麼了?
· 在LIFETIME_WEAK的魔法下,強引用計數爲0,而弱引用計數不爲0的時候,實際對象沒有被delete!只有當強引用計數和弱引用計數同時爲0時,實際對象和影子對象纔會被delete。
至於LIFETIME_FOREVER的破解,就不用再來一斧子了,我直接的答案是:
· flags爲0,強引用計數控制實際對象的生命週期,弱引用計數控制影子對象的生命週期。強引用計數爲0後,實際對象被delete。因此對於這種狀況,應記住的是,使用wp時要由弱生強,以避免收到segment fault信號。
· flags爲LIFETIME_WEAK,強引用計數爲0,弱引用計數不爲0時,實際對象不會被delete。當弱引用計數減爲0時,實際對象和影子對象會同時被delete。這是功德圓滿的狀況。
· flags爲LIFETIME_FOREVER,對象將長生不老,完全擺脫強弱引用計數的控制。因此你要在適當的時候殺死這些老妖精,省得她禍害「人間」。
上面介紹的RefBase,是一個重量級的引用計數控制類。那麼,究竟有沒有一個簡單些的引用計數控制類呢?Android爲咱們提供了一個輕量級的LightRefBase。這個類很是簡單,咱們不妨一塊兒來看看。
[-->RefBase.h]
template <class T>
class LightRefBase
{
public:
inlineLightRefBase() : 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的時候減1,當引用計數變爲零的時候,delete掉本身
if(android_atomic_dec(&mCount) == 1) {
delete static_cast<const T*>(this);
}
}
inlineint32_t getStrongCount() const {
return mCount;
}
protected:
inline~LightRefBase() { }
private:
mutable volatile int32_t mCount;//引用計數控制變量
};
LightRefBase類夠簡單吧?不過它是一個模板類,咱們該怎麼用它呢?下面給出一個例子,其中類A是從LightRefBase派生的,寫法以下:
class A:public LightRefBase<A> //注意派生的時候要指明是LightRefBase<A>
{
public:
A(){};
~A(){};
};
另外,咱們從LightRefBase的定義中能夠知道,它支持sp的控制,由於它只有incStrong和decStrong函數。
從代碼量上看,RefBase、sp和wp的代碼量並很少,但裏邊的關係,尤爲是flags的引入,曾一度讓我眼花繚亂。當時,我確實很但願能本身調試一下這些例子,但在設備上調試native代碼,須要花費很大的精力,即便是經過輸出log的方式也須要不少時間。該怎麼解決這一難題?
既然它的代碼很少並且簡單,那何不把它移植到臺式機的開發環境下,整一個相似的RefBase呢?因爲有了這樣的構想,我便用上了Visual Studio。至於那些原子操做,Windows平臺上有很直接的InterlockedExchangeXXX與之對應,真的是踏破鐵鞋無覓處,得來全不費功夫!(在Linux平臺上,不考慮多線程的話,將原子操做換成普通的非原子操做不是也能夠嗎?若是更細心更負責任的話,你能夠本身用匯編來實現經常使用的原子操做,內核代碼中有現成的函數,一看就會明白。)
若是把破解代碼當作是攻城略地的話,咱們必須學會靈活多變,並且應力求破解方法日臻極致!
Thread類是Android爲線程操做而作的一個封裝。代碼在Thread.cpp中,其中還封裝了一些與線程同步相關(既然是封裝,要掌握它,最重要的固然是與Pthread相關的知識)的類。咱們擬先行分析Threa類,進而再介紹與經常使用同步類相關的知識。
Thread類雖然說挺簡單,但它構造函數中的那個canCallJava卻一度使我感到費解。由於我一直使用的是本身封裝的Pthread類。當發現Thread構造函數中居然存在這樣一個東西時,很擔憂本身封裝的Pthread類會不會有什麼重大問題,由於當時我還歷來沒考慮過Java方面的問題。
// canCallJava表示這個線程是否會使用JNI函數。爲何須要一個這樣的參數呢?
Thread(bool canCallJava = true)。
咱們必須得了解它實際建立的線程函數是什麼。Thread類真實的線程是建立在run函數中的。
先來看一段代碼:
[-->Thread.cpp]
status_t Thread::run(const char* name, int32_tpriority, 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,但調用的函數卻不一樣。先直接看mCanCallJava爲true的這個分支,代碼以下所示:
[-->Thread.h::createThreadEtc()函數]
inline bool createThreadEtc(thread_func_tentryFunction,
void *userData,
const char*threadName = "android:unnamed_thread",
int32_tthreadPriority = PRIORITY_DEFAULT,
size_tthreadStackSize = 0,
thread_id_t*threadId = 0)
{
returnandroidCreateThreadEtc(entryFunction, userData, threadName,
threadPriority, threadStackSize,threadId) ? true : false;
}
它調用的是androidCreateThreadEtc函數,相關代碼以下所示:
// gCreateThreadFn是函數指針,初始化時和mCanCallJava爲false時使用的是同一個
//線程建立函數。那麼有地方會修改它嗎?
static android_create_thread_fn gCreateThreadFn= androidCreateRawThreadEtc;
int androidCreateThreadEtc(android_thread_func_tentryFunction,
void*userData,const char* threadName,
int32_tthreadPriority,size_t threadStackSize,
android_thread_id_t*threadId)
{
returngCreateThreadFn(entryFunction, userData, threadName,
threadPriority,threadStackSize, threadId);
}
若是沒有人修改這個函數指針,那麼mCanCallJava就是虛晃一槍,並沒有什麼做用,很惋惜,代碼中有的地方是會修改這個函數指針的指向的,請看:
在第四章4.2.1的第2小節AndroidRuntime調用startReg的地方,就有可能修改這個函數指針,其代碼以下所示:
[-->AndroidRuntime.cpp]
/*static*/ int AndroidRuntime::startReg(JNIEnv*env)
{
//這裏會修改函數指針爲javaCreateThreadEtc
androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc);
return0;
}
因此,若是mCanCallJava爲true,則將調用javaCreateThreadEtc。那麼,這個函數有什麼特殊之處呢?來看其代碼,以下所示:
[-->AndroidRuntime.cpp]
int AndroidRuntime::javaCreateThreadEtc(
android_thread_func_tentryFunction,
void* userData,
const char*threadName,
int32_tthreadPriority,
size_t threadStackSize,
android_thread_id_t* threadId)
{
void**args = (void**) malloc(3 * sizeof(void*));
intresult;
args[0] = (void*) entryFunction;
args[1] = userData;
args[2] = (void*) strdup(threadName);
//調用的仍是androidCreateRawThreadEtc,但線程函數卻換成了javaThreadShell。
result= androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args,
threadName, threadPriority,threadStackSize, threadId);
returnresult;
}
[-->AndroidRuntime.cpp]
int AndroidRuntime::javaThreadShell(void* args){
......
intresult;
//把這個線程attach到JNI環境中,這樣這個線程就能夠調用JNI的函數了
if(javaAttachThread(name, &env) != JNI_OK)
return -1;
//調用實際的線程函數幹活
result = (*(android_thread_func_t)start)(userData);
//從JNI環境中detach出來。
javaDetachThread();
free(name);
returnresult;
}
你明白mCanCallJava爲true的目的了嗎?它建立的新線程將:
· 在調用你的線程函數以前會attach到 JNI環境中,這樣,你的線程函數就能夠無憂無慮地使用JNI函數了。
· 線程函數退出後,它會從JNI環境中detach,釋放一些資源。
第二點尤爲重要,由於進程退出前,dalvik虛擬機會檢查是否有attach了,可是最後未detach的線程若是有,則會直接abort(這不是一件好事)。若是你關閉JNI check選項,就不會作這個檢查,但我以爲,這個檢查和資源釋放有關係。建議仍是重視JNIcheck。若是直接使用POSIX的線程建立函數,那麼凡是使用過attach的,最後就都須要detach!
Android爲了dalvik的健康真是費盡心機呀。
不論一分爲二是如何處理的,最終的線程函數_threadLoop都會被調用,爲何不直接調用用戶傳入的線程函數呢?莫非_threadLoop會有什麼暗箱操做嗎?下面,咱們來看:
[-->Thread.cpp]
int Thread::_threadLoop(void* user)
{
Thread* const self = static_cast<Thread*>(user);
sp<Thread> strong(self->mHoldSelf);
wp<Thread> weak(strong);
self->mHoldSelf.clear();
#if HAVE_ANDROID_OS
self->mTid = gettid();
#endif
boolfirst = true;
do {
bool result;
if(first) {
first = false;
//self表明繼承Thread類的對象,第一次進來將調用readyToRun,看看是否準備好
self->mStatus = self->readyToRun();
result = (self->mStatus == NO_ERROR);
if (result && !self->mExitPending) {
result = self->threadLoop();
}
}else {
/*
調用子類實現的threadLoop函數,注意這段代碼運行在一個do-while循環中。
這表示即便咱們的threadLoop返回了,線程也不必定會退出。
*/
result = self->threadLoop();
}
/*
線程退出的條件:
1)result 爲false。這代表,若是子類在threadLoop中返回false,線程就能夠
退出。這屬於主動退出的狀況,是threadLoop本身不想繼續幹活了,因此返回false。
讀者在本身的代碼中千萬別寫錯threadLoop的返回值。
2)mExitPending爲true,這個變量可由Thread類的requestExit函數設置,這種
狀況屬於被動退出,由於由外界強制設置了退出條件。
*/
if(result == false || self->mExitPending) {
self->mExitPending = true;
self->mLock.lock();
self->mRunning = false;
self->mThreadExitedCondition.broadcast();
self->mLock.unlock();
break;
}
strong.clear();
strong = weak.promote();
}while(strong != 0);
return0;
}
關於_threadLoop,咱們就介紹到這裏。請讀者務必注意下面一點:
· threadLoop運行在一個循環中,它的返回值能夠決定是否退出線程。
同步,是多線程編程中不可迴避的話題,同時也是一個很是複雜的問題。這裏,只簡單介紹一下Android提供的同步類。這些類,只對系統提供的多線程同步函數(這種函數咱們也稱之爲Raw API)進行了面向對象的封裝,讀者必須先理解Raw API,而後才能真正掌握其具體用法。
瞭解Windows下的多線程編程,有不少參考資料,但我覺得,如今先學習MSDN就能夠了。有關Linux下完整系統闡述多線程編程的書籍目前較少,這裏推薦一本含金量較高的著做《Programmingwith POSIX Thread》(本書只有英文版的,由Addison-Wesley出版)。
Android提供了兩個封裝好的同步類,它們是Mutex和Condition。這是重量級的同步技術,通常內核會有對應的支持。另外,OS還提供了簡單的原子操做,這些也算是同步技術的一種。下面分別來介紹這三種東西。
Mutex是互斥類,用於多線程訪問同一個資源的時候,保證一次只能有一個線程能訪問該資源。在《Windows核心編程》①一書中,對於這種互斥訪問有一個很形象的比喻:想象你在飛機上如廁,這時衛生間的信息牌上顯示「有人」,你必須等裏邊的人出來後纔可進去。這就是互斥的含義。
下面來看Mutex的實現方式,它們都很簡單。
其代碼以下所示:
[-->Thread.h::Mutex的聲明和實現]
inline Mutex::Mutex(int type, const char* name){
if(type == SHARED) {
//type若是是SHARED,則代表這個Mutex支持跨進程的線程同步
//之後咱們在Audio系統和Surface系統中會常常見到這種用法
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mMutex, &attr);
pthread_mutexattr_destroy(&attr);
} else {
pthread_mutex_init(&mMutex, NULL);
}
}
inline Mutex::~Mutex() {
pthread_mutex_destroy(&mMutex);
}
inline status_t Mutex::lock() {
return-pthread_mutex_lock(&mMutex);
}
inline void Mutex::unlock() {
pthread_mutex_unlock(&mMutex);
}
inline status_t Mutex::tryLock() {
return-pthread_mutex_trylock(&mMutex);
}
關於Mutex的使用,除了初始化外,最重要的是lock和unlock函數的使用,它們的用法以下:
· 要想獨佔衛生間,必須先調用Mutex的lock函數。這樣,這個區域就被鎖住了。若是這塊區域以前已被別人鎖住,lock函數則會等待,直到能夠進入這塊區域爲止。系統保證一次只有一個線程能lock成功。
· 當你「方便」完畢,記得調用Mutex的unlock以釋放互斥區域。這樣,其餘人的lock才能夠成功返回。
· 另外,Mutex還提供了一個trylock函數,該函數只是嘗試去鎖住該區域,使用者須要根據trylock的返回值判斷是否成功鎖住了該區域。
注意,以上這些內容都和Raw API有關,不瞭解它的讀者可自行學習與它相關的知識。在Android系統中,多線程也是常見和重要的編程手段,務請你們重視。
Mutex類確實比Raw API方便好用,不過仍是稍顯麻煩。來看下一節。
AutoLock類是定義在Mutex內部的一個類,它實際上是一幫「懶人」搞出來的,爲何這麼說呢?先來看看使用Mutex夠多麻煩:
· 顯示調用Mutex的lock。
· 在某個時候要記住調用該Mutex的unlock。
以上這些操做都必須一一對應,不然會出現「死鎖」!有些代碼中,在判斷分支特別多的狀況下,unlock這句代碼被寫得比比皆是,如稍有不慎,在某處就會忘寫了它。有什麼好辦法能解決這個問題嗎?終於有人想出來一個好辦法,就是充分利用了C++的構造和析構函數,只需一看AutoLock的定義就會明白。代碼以下所示:
[-->Thread.h Mutex::Autolock聲明和實現]
classAutolock {
public:
//構造的時候調用lock
inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
//析構的時候調用unlock
inline ~Autolock() { mLock.unlock(); }
private:
Mutex& mLock;
};
AutoLock的用法很簡單:
· 先定義一個Mutex,如 Mutex xlock;
· 在使用xlock的地方,定義一個AutoLock,如 AutoLock autoLock(xlock)。
因爲C++對象的構造和析構函數都是自動被調用的,因此在AutoLock的生命週期內,xlock的lock和unlock也就自動被調用了,這樣就省去了重複書寫unlock的麻煩,並且lock和unlock的調用確定是一一對應的,這樣就絕對不會出錯。
多線程同步中的條件類對應的是下面一種使用場景:
· 線程A作初始化工做,而其餘線程好比線程B、C必須等到初始化工做完後才能工做,即線程B、C在等待一個條件,咱們稱B、C爲等待者。
· 當線程A完成初始化工做時,會觸發這個條件,那麼等待者B、C就會被喚醒。觸發這個條件的A就是觸發者。
上面的使用場景很是形象,並且條件類提供的函數也很是形象,它的代碼以下所示:
[-->Thread.h::Condition的聲明和實現]
class Condition {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
Condition();
Condition(int type);//若是type是SHARED,表示支持跨進程的條件同步
~Condition();
//線程B和C等待事件,wait這個名字是否是很形象呢?
status_t wait(Mutex& mutex);
//線程B和C的超時等待,B和C能夠指定等待時間,當超過這個時間,條件卻還不知足,則退出等待
status_t waitRelative(Mutex& mutex, nsecs_t reltime);
//觸發者A用來通知條件已經知足,可是B和C只有一個會被喚醒
voidsignal();
//觸發者A用來通知條件已經知足,全部等待者都會被喚醒
voidbroadcast();
private:
#if defined(HAVE_PTHREADS)
pthread_cond_t mCond;
#else
void* mState;
#endif
}
聲明很簡單,定義也很簡單,代碼以下所示:
inline Condition::Condition() {
pthread_cond_init(&mCond, NULL);
}
inline Condition::Condition(int type) {
if(type == SHARED) {//設置跨進程的同步支持
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&mCond, &attr);
pthread_condattr_destroy(&attr);
} else{
pthread_cond_init(&mCond, NULL);
}
}
inline Condition::~Condition() {
pthread_cond_destroy(&mCond);
}
inline status_t Condition::wait(Mutex&mutex) {
return-pthread_cond_wait(&mCond, &mutex.mMutex);
}
inline status_tCondition::waitRelative(Mutex& mutex, nsecs_t reltime) {
#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
structtimespec ts;
ts.tv_sec = reltime/1000000000;
ts.tv_nsec = reltime%1000000000;
return-pthread_cond_timedwait_relative_np(&mCond, &mutex.mMutex, &ts);
...... //有些系統沒有實現POSIX的相關函數,因此不一樣系統須要調用不一樣的函數
#endif
}
inline void Condition::signal() {
pthread_cond_signal(&mCond);
}
inline void Condition::broadcast() {
pthread_cond_broadcast(&mCond);
}
能夠看出,Condition的實現全是憑藉調用了Raw API的pthread_cond_xxx函數。這裏要重點說明的是,Condition類必須配合Mutex來使用。什麼意思?
· 上面代碼中,不管是wait、waitRelative、signal仍是broadcast的調用,都放在一個Mutex的lock和unlock範圍中,尤爲是wait和waitRelative函數的調用,這是強制性的。
來看一個實際的例子,加深一下對Condition類和Mutex類使用的印象。這個例子是Thread類的requestExitAndWait,目的是等待工做線程退出,代碼以下所示:
[-->Thread.cpp]
status_t Thread::requestExitAndWait()
{
......
requestExit();//設置退出變量mExitPending爲true
Mutex::Autolock_l(mLock);//使用Autolock,mLock被鎖住
while(mRunning == true) {
/*
條件變量的等待,這裏爲何要經過while循環來反覆檢測mRunning?
由於某些時候即便條件類沒有被觸發,wait也會返回。關於這個問題,強烈建議讀者閱讀
前邊推薦的《Programming with POSIX Thread》一書。
*/
mThreadExitedCondition.wait(mLock);
}
mExitPending = false;
//退出前,局部變量Mutex::Autolock _l的析構會被調用,unlock也就會被自動調用。
returnmStatus;
}
那麼,什麼地方會觸發這個條件呢?是在工做線程退出前。其代碼以下所示:
[-->Thread.cpp]
int Thread::_threadLoop(void* user)
{
Thread* const self =static_cast<Thread*>(user);
sp<Thread> strong(self->mHoldSelf);
wp<Thread> weak(strong);
self->mHoldSelf.clear();
do {
......
result= self->threadLoop();//調用子類的threadLoop函數
......
//若是mExitPending爲true,則退出
if(result == false || self->mExitPending) {
self->mExitPending = true;
//退出前觸發條件變量,喚醒等待者
self->mLock.lock();//lock鎖住
//mRunning的修改位於鎖的保護中。若是你閱讀了前面推薦的書,這裏也就不難理解了
self->mRunning = false;
self->mThreadExitedCondition.broadcast();
self->mLock.unlock();//釋放鎖
break;//退出循環,此後該線程函數會退出
}
......
}while(strong != 0);
return0;
}
關於Android多線程的同步類,暫時介紹到此吧。固然,這些類背後所隱含的知識及技術是讀者須要倍加劇視的。
但願咱們能養成一種由點及面的學習方法。以咱們的同步類爲例,假設你是第一次接觸多線程編程,也學會了如何使用Mutex和Condition這兩個類,不妨以這兩個類代碼中所傳遞的知識作爲切入點,把和多線程相關的全部知識(這個知識不只僅是函數的使用,還包括多線程的原理,多線程的編程模型,甚至是如今很熱門的並行多核編程)廣泛瞭解一下。只有深入理解並掌握了原理等基礎和框架性的知識,才能以不變應萬變,才能作到遊刃有餘。
什麼是原子操做?所謂原子操做,就是該操做毫不會在執行完畢前被任何其餘任務或事件打斷,也就說,原子操做是最小的執行單位。
上面這句話放到代碼中是什麼意思?請看一個例子:
[-->例子]
static int g_flag = 0; //全局變量g_flag
static Mutex lock ;//全局的鎖
//線程1執行thread1
void thread1()
{
//g_flag遞減,每次操做前鎖住
lock.lock();
g_flag--;
lock.unlock();
}
//線程2中執行thread2函數
void thread2()
{
lock.lock();
g_flag++; //線程2對g_flag進行遞增操做,每次操做前要取得鎖
lock.unlock();
}
爲何須要Mutex來幫忙呢?由於g_flags++或者g_flags—操做都不是原子操做。從彙編指令的角度看,C/C++中的一條語句對應了數條彙編指令。以g_flags++操做爲例,它生成的彙編指令可能就是如下三條:
· 從內存中取數據到寄存器。
· 對寄存器中的數據進行遞增操做,結果還在寄存器中。
· 寄存器的結果寫回內存。
這三條彙編指令,若是按正常的順序連續執行,是沒有問題的,但在多線程時就不能保證了。例如,線程1在執行第一條指令後,線程2因爲調度的緣由,搶先在線程1以前連續執行完了三條指令。這樣,線程1繼續執行指令時,它所使用的值就不是線程2更新後的值,而是以前的舊值。再對這個值進行操做便沒有意義了。
在通常狀況下,處理這種問題可使用Mutex來加鎖保護,但Mutex的使用比它所要保護的內容還複雜,例如,鎖的使用將致使從用戶態轉入內核態,有較大的浪費。那麼,有沒有簡便些的辦法讓這些加、減等操做不被中斷呢?
答案是確定的,但這須要CPU的支持。在X86平臺上,一個遞增操做能夠用下面的內嵌彙編語句實現:
#define LOCK "lock;"
INT32 InterlockedIncrement(INT32* lpAddend)
{
/*
這是咱們在Linux平臺上實現Windows API時使用的方法。
其中在SMP系統上,LOCK定義成」lock;」表示鎖總線,這樣同一時刻只能有一個CPU訪問總線。
非SMP系統,LOCK定義成空。因爲InterlockedIncrement要返回遞增前的舊值,因此咱們
使用了xaddl指令,它先交換源和目的的操做數,再進行遞增操做。
*/
INT32i = 1;
__asm____volatile__(
LOCK"xaddl %0, %1"
:"+r"(i), "+m" (*lpAddend)
:: "memory");
return*lpAddend;
}
Android提供了相關的原子操做函數。這裏,有必要介紹一下各個函數的做用。
[-->Atomic.h],注意該文件位置在system/core/include/cutils目錄中。
//原子賦值操做,結果是*addr=value
void android_atomic_write(int32_t value,volatile int32_t* addr);
//下面全部函數的返回值都是操做前的舊值
//原子加1和原子減1
int32_t android_atomic_inc(volatile int32_t*addr);
int32_t android_atomic_dec(volatile int32_t*addr);
//原子加法操做,value爲被加數
int32_t android_atomic_add(int32_t value,volatile int32_t* addr);
//原子「與」和「或」操做
int32_t android_atomic_and(int32_t value,volatile int32_t* addr);
int32_t android_atomic_or(int32_t value,volatile int32_t* addr);
/*
條件交換的原子操做。只有在oldValue等於*addr時,纔會把newValue賦值給*addr
這個函數的返回值須特別注意。返回值非零,表示沒有進行賦值操做。返回值爲零,表示
進行了原子操做。
*/
int android_atomic_cmpxchg(int32_t oldvalue,int32_t newvalue,
volatile int32_t*addr);
有興趣的話,讀者能夠對上述函數的實現進行深刻研究,其中,
· X86平臺的實如今system/core/libcutils/Atomic.c中,注意其代碼在#elif defined(__i386__) || defined(__x86_64__)所包括的代碼段內。
· ARM平臺的實如今system/core/libcutils/atomic-android-arm.S彙編文件中。
原子操做的最大好處在於避免了鎖的使用,這對整個程序運行效率的提升有很大幫助。目前,在多核並行編程中,最高境界就是徹底不使用鎖。固然,它的難度可想而知是巨大的。
就應用程序而言,Android系統中Java的和其餘系統上的相同,是靠消息驅動來工做的,它們大體的工做原理以下:
· 有一個消息隊列,能夠往這個消息隊列中投遞消息。
· 有一個消息循環,不斷從消息隊列中取出消息,而後處理。
咱們用圖5-1來展現這個工做過程:
圖5-1 線程和消息處理原理圖
從圖中能夠看出:
· 事件源把待處理的消息加入到消息隊列,通常是加至隊列尾,一些優先級高的消息也能夠加至隊列頭。事件源提交的消息能夠是按鍵、觸摸屏等物理事件產生的消息,也能夠是來自系統或應用程序自己發出的請求消息。
· 處理線程不斷從消息隊列頭中取出消息並處理,事件源能夠把優先級高的消息放到隊列頭,這樣,優先級高的消息就會首先被處理。
在Android系統中,這些工做主要由Looper和Handler來實現:
· Looper類,用於封裝消息循環,而且有一個消息隊列。
· Handler類,有點像輔助類,它封裝了消息投遞,消息處理等接口。
Looper類是其中的關鍵。先來看看它是怎麼作的。
咱們以Looper使用的一個常見例子來分析Looper類。
[-->例子1]
//定義一個LooperThread
class LooperThread extends Thread {
publicHandler mHandler;
public void run() {
//① 調用prepare
Looper.prepare();
......
//② 進入消息循環
Looper.loop();
}
}
//應用程序使用LooperThread
{
......
newLooperThread().start();//啓動新線程,線程函數是run
}
上面的代碼一共有兩個關鍵調用,咱們對其逐一進行分析。
第一個調用函數是Looper的prepare函數。它會作什麼工做呢?其代碼以下所示:
[-->Looper.java]
publicstatic final void prepare() {
//一個Looper只能調用一次prepare
if(sThreadLocal.get() != null) {
thrownew RuntimeException("Only one Looper may be created per thread");
}
//構造一個Looper對象,設置到調用線程的局部變量中
sThreadLocal.set(newLooper());
}
//sThreadLocal定義
private static final ThreadLocal sThreadLocal =new ThreadLocal();
ThreadLocal是Java中的線程局部變量類,全名應該是Thread Local Variable。我以爲,它的實現和操做系統提供的線程本地存儲(TLS)有關係。總之,該類有兩個關鍵函數:
· set:設置調用線程的局部變量。
· get:獲取調用線程的局部變量。
注意,set/get的結果都和調用這個函數的線程有關。ThreadLocal類可參考JDK API文檔或Android API文檔。
根據上面的分析可知,prepare會在調用線程的局部變量中設置一個Looper對象。這個調用線程就是LooperThread的run線程。先看看Looper對象的構造,其代碼以下所示:
[-->Looper.java]
private Looper(){
//構造一個消息隊列
mQueue =new MessageQueue();
mRun =true;
//獲得當前線程的Thread對象
mThread =Thread.currentThread();
}
prepare函數很簡單,它主要乾了一件事:
· 在調用prepare的線程中,設置了一個Looper對象,這個Looper對象就保存在這個調用線程的TLV中。而Looper對象內部封裝了一個消息隊列。
也就是說,prepare函數經過ThreadLocal機制,巧妙地把Looper和調用線程關聯在一塊兒了。要了解這樣作的目的是什麼,須要再看第二個重要函數。
代碼以下所示:
[-->Looper.java]
public static final void loop() {
Looper me = myLooper();//myLooper返回保存在調用線程TLV中的Looper對象
//取出這個Looper的消息隊列
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next();
//處理消息,Message對象中有一個target,它是Handler類型
//若是target爲空,則表示須要退出消息循環
if (msg != null) {
if (msg.target == null) {
return;
}
//調用該消息的Handler,交給它的dispatchMessage函數處理
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
//myLooper函數返回調用線程的線程局部變量,也就是存儲在其中的Looper對象
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
經過上面的分析會發現,Looper的做用是:
· Looper封裝了一個消息隊列。
· Looper的prepare函數把這個Looper和調用prepare的線程(也就是最終的處理線程)綁定在一塊兒了。
· 處理線程調用loop函數,處理來自該消息隊列的消息。
當事件源向這個Looper發送消息的時候,實際上是把消息加到這個Looper的消息隊列裏了。那麼,該消息就將由和Looper綁定的處理線程來處理。那麼,事件源又是怎麼向Looper消息隊列添加消息的呢?來看下一節。
Looper、Message和Handler之間也存在曖昧關係,不過要比RefBase那三個簡單得多,用兩句話就能夠說清楚:
· Looper中有一個Message隊列,裏邊存儲的是一個個待處理的Message。
· Message中有一個Handler,這個Handler是用來處理Message的。
其中,Handler類封裝了不少瑣碎的工做。先來認識一下這個Handler。
Handler中所包括的成員:
[-->Handler.java]
final MessageQueue mQueue;//Handler中也有一個消息隊列
final Looper mLooper;//也有一個Looper
final Callback mCallback;//有一個回調用的類
這幾個成員變量是怎麼使用的呢?這首先得分析Handler的構造函數。Handler一共有四個構造函數,它們主要的區別,是在對上面三個重要成員變量的初始化上。咱們試對其進行逐一分析。
[-->Handler.java]
//構造函數1
public Handler() {
//得到調用線程的Looper
mLooper = Looper.myLooper();
if(mLooper == null) {
throw new RuntimeException(......);
}
//獲得Looper的消息隊列
mQueue = mLooper.mQueue;
//無callback設置
mCallback = null;
}
//構造函數2
publicHandler(Callback callback) {
mLooper = Looper.myLooper();
if(mLooper == null) {
throw new RuntimeException(......);
}
//和構造函數1相似,只不過多了一個設置callback
mQueue = mLooper.mQueue;
mCallback = callback;
}
//構造函數3
publicHandler(Looper looper) {
mLooper = looper; //looper由外部傳入,是哪一個線程的Looper不肯定
mQueue = looper.mQueue;
mCallback = null;
}
//構造函數4,和構造函數3相似,只不過多了callback設置
publicHandler(Looper looper, Callback callback) {
mLooper= looper;
mQueue = looper.mQueue;
mCallback = callback;
}
在上述構造函數中,Handler中的消息隊列變量最終都會指向了Looper的消息隊列,Handler爲什麼要如此作?
根據前面的分析可知,Handler中的消息隊列實際就是某個Looper的消息隊列,那麼,Handler作如此安排的目的何在?
在回答這個問題以前,我先來問一個問題:
· 怎麼往Looper的消息隊列插入消息?
若是不知道Handler,這裏有一個很原始的方法:
· 調用Looper的myQueue,它將返回消息隊列對象MessageQueue。
· 構造一個Message,填充它的成員,尤爲是target變量。
· 調用MessageQueue的enqueueMessage,將消息插入消息隊列。
這種原始方法的確很麻煩,且極容易出錯。但有了Handler後,咱們的工做就變得異常簡單了。Handler更像一個輔助類,幫助咱們簡化編程的工做。
Handle提供了一系列函數,幫助咱們完成建立消息和插入消息隊列的工做。這裏只列舉其中一二。要掌握詳細的API,則須要查看相關文檔。
//查看消息隊列中是否有消息碼是what的消息
final boolean hasMessages(int what)
//從Handler中建立一個消息碼是what的消息
final Message obtainMessage(int what)
//從消息隊列中移除消息碼是what的消息
final void removeMessages(int what)
//發送一個只填充了消息碼的消息
final boolean sendEmptyMessage(int what)
//發送一個消息,該消息添加到隊列尾
final boolean sendMessage(Message msg)
//發送一個消息,該消息添加到隊列頭,因此優先級很高
final boolean sendMessageAtFrontOfQueue(Message msg)
只需對上面這些函數稍做分析,就能明白其餘的函數。現以sendMessage爲例,其代碼以下所示:
[-->Handler.java]
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0); //調用sendMessageDelayed
}
[-->Handler.java]
// delayMillis是以當前調用時間爲基礎的相對時間
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//調用sendMessageAtTime,把當前時間算上
return sendMessageAtTime(msg,SystemClock.uptimeMillis() + delayMillis);
}
[-->Handler.java]
//uptimeMillis 是絕對時間,即sendMessageAtTime函數處理的是絕對時間
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
//把Message的target設置爲本身,而後加入到消息隊列中
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}
看到上面這些函數能夠想見,若是沒有Handler的輔助,當咱們本身操做MessageQueue的enqueueMessage時,得花費多大功夫!
Handler把Message的target設爲本身,是由於Handler除了封裝消息添加等功能外還封裝了消息處理的接口。
剛纔,咱們往Looper的消息隊列中加入了一個消息,按照Looper的處理規則,它在獲取消息後,會調用target的dispatchMessage函數,再把這個消息派發給Handler處理。Handler在這塊是如何處理消息的呢?
[-->Handler.java]
public void dispatchMessage(Message msg) {
//若是Message自己有callback,則直接交給Message的callback處理
if(msg.callback != null) {
handleCallback(msg);
}else {
//若是本Handler設置了mCallback,則交給mCallback處理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最後纔是交給子類處理
handleMessage(msg);
}
}
dispatchMessage定義了一套消息處理的優先級,它們分別是:
· Message若是自帶了callback處理,則交給callback處理。
· Handler若是設置了全局的mCallback,則交給mCallback處理。
· 若是上述都沒有,該消息則會被交給Handler子類實現的handleMessage來處理。固然,這須要從Handler派生並重載handleMessage函數。
在一般狀況下,咱們通常都是採用第三種方法,即在子類中經過重載handleMessage來完成處理工做的。
至此,Handler知識基本上講解完了,但是在實際編碼過程當中還有一個重要問題須要警戒。下一節內容就將談及此問題。
Looper和Handler會有什麼同步關係呢?它們之間確實有同步關係,並且若是不注意此關係,定要鑄成大錯!
同步關係確定和多線程有關,看下面的一個例子:
[-->例子2]
//先定義一個LooperThread類
class LooperThread extends Thread {
publicLooper myLooper = null;//定義一個public的成員myLooper,初值爲空。
public void run() { //假設run在線程2中執行
Looper.prepare();
// myLooper必須在這個線程中賦值
myLooper = Looper.myLooper();
Looper.loop();
}
}
//下面這段代碼在線程1中執行,而且會建立線程2
{
LooperThreadlpThread= new LooperThread;
lpThread.start();//start後會建立線程2
Looper looper = lpThread.myLooper;//<======注意
// thread2Handler和線程2的Looper掛上鉤
Handler thread2Handler = new Handler(looper);
//sendMessage發送的消息將由線程2處理
threadHandler.sendMessage(...)
}
上面這段代碼的目的很簡單:
· 線程1中建立線程2,而且線程2經過Looper處理消息。
· 線程1中獲得線程2的Looper,而且根據這個Looper建立一個Handler,這樣發送給該Handler的消息將由線程2處理。
但很惋惜,上面的代碼是有問題的。若是咱們熟悉多線程,就會發現標有「注意」的那行代碼存在着嚴重問題。myLooper的建立是在線程2中,而looper的賦值則在線程1,頗有可能此時線程2的run函數還沒來得及給myLooper賦值,這樣線程1中的looper將取到myLooper的初值,也就是looper等於null。另外,
Handler thread2Handler = new Handler(looper) 不能替換成
Handler thread2Handler = new Handler(Looper.myLooper())
這是由於,myLooper返回的是調用線程的Looper,即Thread1的Looper,而不是咱們想要的Thread2的Looper。
對這個問題,能夠採用同步的方式進行處理。你是否是有點火燒眉毛地想完善這個例子了?其實Android早就替咱們想好了,它提供了一個HandlerThread來解決這個問題。
HandlerThread完美地解決了myLooper可能爲空的問題。來看看它是怎麼作的。代碼以下所示:
[-->HandlerThread]
public class HandlerThread extends Thread{
//線程1調用getLooper來得到新線程的Looper
publicLooper getLooper() {
......
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//若是新線程還未建立Looper,則等待
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
//線程2運行它的run函數,looper就是在run線程裏建立的。
publicvoid run() {
mTid = Process.myTid();
Looper.prepare(); //建立這個線程上的Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//通知取Looper的線程1,此時Looper已經建立好了。
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
HandlerThread很簡單,小小的wait/ notifyAll就解決了咱們的難題。爲了不重複發明輪子,咱們仍是多用HandlerThread類吧!
本章主要分析了Android代碼中最多見的幾個類:其中在Native層包括與對象生命週期相關的RefBase、sp、wp、LightRefBase類,以及Android爲多線程編程提供的Thread類和相關的同步類;Java層則包括使用最爲普遍的Handler類和Looper類。另外,還分析了方類HandlerThread,它下降了建立和使用帶有消息隊列的線程的難度。