Android四大組件對Android開發者開說再熟悉不過了,他們是Activity、Service、BroadcastReceiver和ContentProvider。當具有一定開發實踐後會發現很多情況情況下,只有對Android體系結構有一定認識,在實際開發中才能寫出更加優秀的代碼,否者總是隻知其表,不知其裏,難免有一種霧裏看花的感覺。
1.四大組件的運行狀態
Android四大組件中BroadcastReceiver既可以在AndroidMenifest.xml中也可以通過java代碼完成註冊,兩者分別稱爲靜態註冊和動態註冊。而其他三者必須在AndroidMenifest.xml中完成註冊。從調用方式來講,Activity、Service、BroadcastReceiver都需要藉助Intent,而ContentProvider不需要藉助Intent。
Activity的調用分爲顯示和隱式兩種,顯式調用無非就是使用startActivity方法,可以明確地指向一個Activity組件,簡單易用不再介紹。隱式調用具體實現在《Android的IntentFilter的匹配規則》中已有詳細描述,不在贅述。但隱式Intent指向一個或多個Activity組件,當然也可能沒有Activity符合篩選規則。Activity組件的主要作用是展示一個界面並和用戶交互,它扮演的其實是一個前臺界面的角色。
Service是一種計算型組件,用於在手機後臺執行一系列計算任務。Service有兩種狀態,分爲啓動狀態和綁定狀態兩種,兩者的不同之處在於後者情況下外界可以很方便的和Service組件進行通信,而前者的Service不需要和外界有直接的交互。但需要注意一點,Service本身是處於主線程的,所以耗時的後臺計算仍然需要放到單獨的線程中完成。
BroadcastReceiver是一種消息型組件,用於在不同的組件乃至不同的應用之間傳遞消息,他事實上工作在系統內部。靜態註冊的廣播不需要應用啓動就可以接收相應的廣播。動態廣播需要通過Context.registerReceiver()來實現,並且在不需要時使用Context.unRegisterReceiver()來解除廣播,這種形式的廣播必須啓動應用才能完成註冊並接收廣播。發送和接收過程的匹配通過廣播接收者的<intent-filter>完成。可以發現,廣播其實是一種低耦合的觀察者模式,也不適合做耗時的操作。
ContentProvider是一種數據共享型的組件,用於向其他組件或者其他應用提供數據。ContentProvider提供增刪改查四個方法,內部維持一個數據集合,這個集合可以使用數據庫實現,也可以使用其他任何類型,沒有什麼要求。因爲這四個方法是在Binder線程池中被調用的,所以處理好線程同步是很重要的。
2.Activity的工作過程
Activity的startActivity()有好幾種重載方式,這在我們調用它的時候就會發現,但該方法內部都會調用startActivityForReult方法。下面是截取該方法的部分代碼
public void startActivityForResult(Intent intent , int requestCode, @Nullable Bundle options)
{
If(mParent == null){
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(......);
.......
}
.......
}
mParent代表的是ActivityGroup,在API 13之後基本使用Fragment取代ActivityGroup了。下面再看exceStartActivity方法,該方法有一個ApplicationThread類型的參數,而ApplicationThread實際上是ActivityThread的一個內部類,瞭解Activity啓動模式的朋友就會發現ApplicationThread和ActivityThread在Activity的啓動過程中多麼重。在該方法中有兩句重要的代碼:
......
int result = ActivityManagerNative.getDefault().startActivity(.......);
checkStartActivityResult(result,intent);
......
第二句很明顯是檢查啓動Activity的結果,如果無法正確的啓動一個Activity就會拋出異常錯誤。啓動Activity的時候有一個常見錯誤“Unable to find explicit activity class;have you declared this activity int your AndroidMenifest.xml”,就是從這拋出的,看到這有疑惑的朋友你是不是豁然開朗了。
既然啓動Activity是上面的第一句代碼完成的,ActivityManagerNative.getDefault()的具體實現的ActivityManagerService(簡稱AMS),所以查看一下AMS的startActivity方法即可。在該方法中又繞了一大圈,最終回到AplicationThread的scheduleLaunchActivity()啓動Activity。在scheduleLaunchActivity方法中發送一個啓動Activity的消息交給Handler處理,這個Handler的名字很簡潔,叫H。後面介紹其他三個組件的啓動時都會用到H。處理這個啓動消息的的時候會先判斷是否Application已經創建過了,如果已經創建過了就不會重複創建了,這也就揭示了爲什麼一個應用只會有一個Application。Application創建完畢後,系統緊接着調用Application的onCreate方法。接着會通過Activity的attach方法來完成一些重要數據的初始化,完成Window的創建並關聯Activity,這樣當Window接收到外部事件後就可以將事件傳遞給Activity。最後就是調用Activity的onCreate方法,完成整個啓動過程。
3.Service的啓動過程
Service有啓動和綁定兩種狀態,值得注意的是,這兩種狀態是可以共存的,也就是說Service可以同時處於啓動和綁定狀態。
啓動一個Service如下所示:
Intent intentService = new Intent(this,oneService.class);
startService(intentService);
通過綁定的方式啓動一個Service:
Intent intentService = new Intent(this,oneService.class);
bindService(intentService,mServiceConnection,BIND_AUTO_CREATE);
Service的啓動從ContextWrapper的startService方法開始,該方法又調用了ContextImpl的startService方法。之前已經說過Context的具體實現交給了ContextImpl來完成,ContextImpl的startService源碼很複雜,分析Service的啓動過程沒必要一句一句代碼的深究。和Activity的啓動類似,最終都是發送消息給Handler H來完成啓動。H通過ActivityThread的handleCreateService完成Service的啓動。
handleCreateService主要完成下面幾件事:
1.通過類加載器創建Service實例;
2.創建Application對象,並調用Application的onCreate方法,當然Application只能創建一次;
3.創建ConTextImpl並通過Service的attach建立兩者的關係,注意Service和Actvity都是一個Context;
4.最後調用Service的onCreate方法,並將Service對象保存到ActivityThread的一個ArrayMap列表中。到這裏Service已經啓動了,下面就是Service的綁定過程。
Service的綁定同樣是從ContextWrapper開始的,再最終調用ContextImpl的bandServiceCommon方法。這個方法主要做了下面的兩件事:
· 把ServiceConnection對象轉化爲ServiceDiapatcher.InnerConnection。這是因爲服務的綁定可能是跨進程的,所以ServiceConnection必須藉助Binder才能讓遠程服務端回調自己的方法,InnerConnection正好充當Binder這個角色。在ServiceDiapatcher中保存了ServiceConnection和InnerConnection對象。
· bindServiceCommon通過AMS的bindService完成具體的綁定過程。AMS通過一系列的調用,最終調用了ApplicationThread的SchedualBindService,在ApplicationThread中一系列由schedule開頭的方法都是通過Handler H來中轉的,這次也不例外。在H內部,會交給ActivityThread的handleBindService處理,首先根據Service的token取出Service對象,然後調用Service的onBind方法,onBind方法會返回一個Binder對象給客戶端使用。這個時候客戶端並不知道已經成功連接Service了,所以需要調用客戶端中ServiceConnection的onServiceConnected方法。到這裏,Service的綁定過程就算完成了。
4.BroadcastReceiver的工作過程
先回顧一下BroadcastReceiver的使用方法,首先要定義廣播的接收者,只需要繼承BroadcastReceiver並重寫onReceiver方法即可。比如:
public class oneReceiver extends BroadcastReceiver{
@Override
public void onReceiver(Context context,Intent intent){
//onReceiver不能做耗時的工作
Log.e(“......”,intent.getAction()+””);
}
}
定義過接收者後,還需要註冊廣播接收者:
·靜態註冊
<receiver android:name= “.oneReceiver”>
<intent-filter>
<action android:name = “com.ryg.receiver.LAUNCH”/>
<intent-filter>
</receiver>
·動態註冊
IntentFilter filter = new IntentFilter();
filter.addAction(“com.ryg.receiver.LAUNCH”);
registerReceiver(new oneReceiver(),filter);
·發送廣播
Intent intent = new Intent();
intent.setAction(“com.rgy.receiver.LAUNCH”);
sendBroadcast(intent);
當使用send方法發送廣播時,AMS會查找出匹配的廣播接收者並把廣播發送給他們處理。廣播分爲普通廣播、有序廣播和粘性廣播。最普遍的還是普通廣播。
廣播的發送和接受實質是一個過程的兩個階段。在Ansroid5.0中,默認情況不會發送廣播給已經停止的應用,實際上在Android3.0就爲Intent添加了兩個標誌位,分別是:
·FLAG_INCLUDE_STOPPED_PACKAGES,表示廣播會發送給已經停止的應用
·FLAG-EXCLUDE_STOPPED_PACKAGES,表示廣播不會發送給已經停止的應用
系統默認爲Intent添加了第二種標誌位,以免廣播無意間或者喚起不必要的應用。如果確實要喚起已經停止的應用,爲廣播添加上面的第一種標誌位即可。當兩種標誌位共存時,廣播會發送給已經停止的應用。而最終廣播發送的實現還是在前面提到過的Handler H類中完成。
5.ContentProvider的工作過程
ContentProvider是一種內容共享型組件,他通過Binder向其他組件乃至其他應用提供數據。當ContentProvider所在的進程啓動時,ContentProvider會同時啓動,並且要注意ContentProvider的onCreate是優先於Application的onCreate執行的。這和前面介紹的三個組件是完全不同的。
當一個應用啓動時,入口方法爲ActivityThread的main方法,在main方法中會創建ActivityThread的實例並創建主線程的消息隊列。在ActivityThread的handleBandApplication方法中,ActivityThread會創建Application對象並加載ContentProvider。這裏要注意的是,ActivityThread會先加載ContentProvider再調用Application的onCreate方法。流程圖如下:
ContentProvider是否是單實例是由他的multiprocess屬性決定的。當android:multiprocess爲false,ContentProvider是單實例的,當他是true的時候,ContentProvider就是多實例。多實例是指每個調用者的進程都會有一個ContentProvider對象,但這樣會帶來進程間通信的開銷,所以大部分還是使用單實例。
當ContentProvider所在進程還未啓動時,第一次訪問他會出發ContentProvider的創建,當然也伴隨着ContentProvider所在進程的啓動。ContentProvider四個方法中的任何一個都會觸發啓動ContentProvider,下面以查詢query爲例。
在query方法中會調用ActivityThread的acquireProvider方法,該方法會先從ActivityThread中查找是否已經存在目標ContentProvider了,如果存在直接返回。如果目前ContentProvider還沒啓動,那麼久發送一條進程間請求給AMS讓其啓動ContentProvider。啓動ContentProvider的時候會先啓動它所在的進程(也可以理解爲是應用),然後再啓動當前進程的ContentProvider。AMS會把ContentProvider存儲在ProviderMap,這樣一來調用者就可以直接從AMS中獲取ContentProvider了。