Android組件模型解析
Android中的Mashup
將應用切分紅不一樣類別的組件,經過統一的定位模型和接口標準將他們整合在一塊兒,來共同完成某項任務。
在Android的Mashup模式下,每一個組件的功能均可以被充分的複用。來自不一樣應用的組件能夠有機地結合在一塊兒,共同完成任務。前端
基於Mashup的Android應用模型
三個基本要素:組件、鏈接、配置android
接口就是實現單元。
從代碼來看,組件就是派生自特定接口或基類的子類的實現,如界面組件Activity就是指派生自android.app.Activity類的子類實現。web
鏈接指的是組件與組件之間的通訊通道,是android爲不一樣類別的組件之間進行調用和通訊預設的模式。好比,與界面組件的通訊鏈接,經過Intent對象來創建,與數據源組件的通訊,則經過URI地址來定位並搭建鏈接通路數據庫
配置是用來描述組件的功能和實現特徵的信息。
Android的組件管理服務,就是經過配置文件中的信息去了解每一個組件的特徵。數組
以發送郵件爲例子緩存
基於Mashup的應用架構特徵
核心是組件,在安卓中,組件執行時的聚合單元是任務(Task),每一個任務都由若干個界面組件對象構成,組件可能來自不一樣的應用,運行在不一樣的進程中,它們彼此獨立,無需關注具體調用者或被調用者的實現細節。
組件間的數據傳輸,都是經過消息、進程間的通訊模型等序列化數據傳輸的方式來進行,而不是經過對象指針的直接傳遞,這就使得Android的應用天生具備了良好的跨進程特徵。網絡
界面組件Activity解析
android的界面組件並無沿用MVC架構,它的設計理解更接近於Web頁面。畢竟,Android應用的架構思想就源自於Web2.0中的Mashup的概念。數據結構
- 從運行模式來看,Android是個多任務的操做系統,能夠同時運行多個任務,每一個任務都有一個界面組件棧,棧中的元素是界面組件對象的實例,其中負責與用戶進行交互的是前臺任務的棧頂組件。
- Android的界面組件是用過類型信息,數據URI信息,數據類型信息等描述信息進行定位的。而界面組件的切換和數據傳輸,都依賴於Android組件管理服務的統一調度和傳遞。
- Android界面組件的功能設計和Web頁面相似,都近似於功能黑盒。在Web開發中,會經過Cooik來存儲一些狀態信息,出於一樣的設計考慮,Android的每一個應用進程都有一個應用環境對象(Application Context),小數據量的共享數據能夠經過它來進行存儲。
【處理構造界面】經過R類的幫助,使用setContentView()方法。
【處理交互事件】一類是在當前界面的全局事件,能夠經過重載Activity中特定的方法來實現,另外一類則是和具體控件相關的交互時間,Android的控件採用了觀察者模式,能夠經過添加監聽者處理相關事件。
【管理界面組件的數據】Android是一個多任務的操做系統,同時運行的任務過多時,就須要自動結束部分應用和組件,以保證系統有充足的內存空間來執行新的任務。Android採用進程託管的策略。對於開發者,須要依照界面組件的生命週期模型,妥善維護好相關的狀態,在組件被銷燬時序列化保存相關的信息,當應用被從新構造時精準地恢復成銷燬前的狀態,以保證用戶體驗的一致性。
【配置界面組件的任務模型】Android界面組件在運行時,會經過任務進行組織。同一個任務中的界面組件,會按照棧模型線性排列。好比,當一個節目組件須要佔用大量資源的時候,就不該該有太多的實例同時存在於人物中,而是要可能多地進行復用,以下降系統的開銷。爲此,android提供了多種組件任務模型,來調整棧中元素的次序,或者是將單個任務拆分紅多個任務,甚至將任務放到不一樣的進程中去,以提高執行效率,經過配置文件中的launchMode、clearTaskOnLaunch、Process等參數進行設置,在使用其餘界面組件時,也須要經過Intent的標誌位(flags)來控制目標組件的任務模型。
【適應環境配置變化】不少配置信息會隨着設備和環境因素的變動而有所改變,好比硬鍵盤消失,屏幕朝向,語言環境等。當配置信息產生變化時,正在與用戶進行交互的界面組件須要根據這些變化及時做出調整,爲用戶提供最合適的交互方式。在默認狀況下,當配置信息發生變化時,Android會簡單地銷燬當前交互的界面組件對象,並根據新的配置信息從新構建該組件對象。若是不指望組件隨着某個配置信息進行銷燬重建,能夠經過activity配置項configChanges來標明。並在Activity.onConfigurationChanged函數中更精細地處理相關配置的變化事件。架構
界面組件的數據結構
Activity派生自Context類,Context類提供了應用運行的基本環境,是各組件和系統服務通訊的橋樑。
Context類是個抽象類,ContextImpl派生實現了它的抽象接口。
ContextImpl對象會與Android框架層的各個服務創建遠程鏈接,經過Andorid進程間的通訊機制(IPC)和這些服務進行通訊。
經過Context的抽象和封裝,隱藏了應用與系統服務通訊的細節,簡化了上層應用的開發。ContextImpl是在Android組件管理服務構造各組件對象時被實例化的。
ContextWrapper的設計應用了修飾模式,它派生自Context,其中的具體實現都是經過組合的方式調用ContextImpl的實例來完成的。這樣的設計,使得ContextImpl與ContextWrapper子類的實現能夠單獨變化,彼此獨立。
Android的界面組件Activity、服務組件Service以及應用基類Application都派生於ContentWrapper,他們能夠經過重載來修改Context接口的實現併發
服務組件Service解析
Android的服務組件派生自Service類,
從運行模式上來看,Android的服務組件沒有運行在獨立的進程或線程中。默認狀況下,服務組件構造於應用進程中,而且和全部其餘的Android組件同樣,都在進程的主線程(即UI線程)中運行。這就意味着,若是直接在服務組件中同步執行耗時的操做,就會致使主線程阻塞或界面假死,從而沒法響應用戶的操做。從使用方式來看,服務組件能夠與前端界面組件創建雙向鏈接,提供數據和功能支持,也能夠單向接受Intent對象的請求,進行數據的分析處理和功能調度。
服務組件的功能和特徵
以鬧鐘爲例:
在這種模式下,服務組件扮演的角色是功能調度者。從事件觸發器對象那裏收集各種事件信息,進一步分析和處理,而後更新界面、修改數據抑或進行其餘相關的存在,調度整個應用使其保持正確的狀態。
服務組件還能夠扮演另外一個很重要的角色,界面組件的功能提供者。在有的場景下,應該須要停留在本身的交互界面與用戶交流。此時它不須要複用第三方界面,而只須要得到一些功能和狀態數據便可,這樣的支持就是經過服務組件來提供的。
輸入法框架便是一個基於服務組件進行復用的例子。
輸入法界面組件經過調用bindService函數發起鏈接請求。輸入法服務(InputMethodService)的onBind方法會被調用並構造一個IBinder對象返回給輸入法界面組件,從而創建一個IPC鏈接,界面組件能夠經過遠程方法調用來進行輸入法相關操做,結束服務後,應定義unbindService函數終止鏈接。
服務組件的開發和使用
構造Service的子類,將其註冊在配置文件中。
構造一個扮演調度者角色的服務組件須要實現的函數是onStartCommand,須要注意的是,在Android中,全部的組件都是在主線程上構造的,所以,onStartCommand函數的執行會阻塞主線程。若是涉及數據庫讀寫、網絡通訊、複雜運算等耗時操做,那麼就須要將相關操做放入獨立的進程或線程中去執行。
將組件放入獨立的進程中,能夠經過配置文件的process參數來實現。
但這樣的方式會增長進程開銷,另外一種可行的策略是在服務組件中另起一個獨立的線程,將那些耗時又費力的操做交給他來打理。
最簡單的實現策略是經過派生IntentService來執行服務組件中的處理邏輯。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
IntentService的實現原理:自己是Service的子類,啓動時,會在onCreate函數中創建一個後臺線程,該線程經過其獨立的消息循環在後臺等待事件,當onStartCommand函數接到相關的Intent參數時,主線程會將其中的內容打包發送到後臺進程中,在onHandleIntent函數中進行處理,從實現來看,只需把本應放在onStartCommand中執行的邏輯內容挪到onHandleIntent中便可。若是用過bindService函數來綁定服務組件創建鏈接,那麼要處理的就是onBind函數,onBind函數會接收到經過bindService函數發出的Intent對象,依照Intent對象的不一樣,Service.onBind函數會返回對應的IBinder對象,整個綁定過程就是一個異步操做,組件管理服務參與其中進行調度。onBind函數返回的IBinder對象,會經過調用ServiceConnection對象的onServiceConnected方法傳遞給調用者,調用者拿到這個IBinder對象後,就能夠經過它與服務組件進程遠程方法調用。
當不須要與服務組件通訊時,調用unbindService方法關閉鏈接。
服務組件的進程間通訊模型
在實際應用中,前臺界面組件和後臺服務組件可能來自不一樣的應用,這就須要進程間通訊。android的進程間通訊模型主要包含三個方面:
【Android的進程間通訊模型架構】典型的代理模式(Proxy Pattern)
在Proxy對象中,每一個函數接口都有一個對應的指令值。當調用者調用這個接口時,Proxy會將數據和指令序列化成一個消息,發送到遠端的Stub對象。Stub對象會拆解出對應的指令和數據,並根據指令執行對應的邏輯,將結果返回給接口調用者,整個流程對於調用者而言徹底透明。
Proxy數據包的發生和回傳工做,是經過Binder來實現的。在Binder對象中,有一個後臺消息循環線程,Proxy傳來的消息包會扔到消息隊列中等待解析和處理,Stub對象都是Binder的子類型,在服務端被實例化,其接口和實現與Proxy一一匹配,負責將Proxy傳遞過來的消息進行解包,獲得其中的指令和數據。在實際應用中。每一個Stub都會有一個實現子類Implement,其實是由它來真正負責在Binder的後臺線程中執行所調用的功能。
界面組件能夠經過bindService得到IBinder的對象,經過它的靜態方法asInterface能夠得到Proxy對象實例。界面端使用Proxy對象,就能夠實現對服務端功能的遠程調用。
對於IPC方法的調用是一個同步的流程,若是執行時間過長,就會阻塞調用方的線程,這時候須要用到異步IPC調用。
構造一個異步的IPC方法調用,須要傳入另外一個IInterface對象,它的Stub對象(圖中的Callback.Stub)在調用組件,而它的Porxy對象(圖中的Callback.Proxy)會隨着序列化傳輸到服務組件,供服務端在操做執行完成後回調通知。服務組件完成操做後,會轉換角色扮演調用方,經過Callback.Proxy對象中的方法,將執行結果通知給調用組件。
與第一次調用不一樣,經過Callback.Proxy對象中的方法,這個調用一般是一個非阻塞性質的,即服務組件不等調用組件執行完成後便馬上返回。
【框架代碼自動生成】整個進程通訊模型中有固定的類型和方法須要實現,包含不少瑣碎而雷同的序列化與反序列化操做。經過AIDL(Android Interfacr Deifinition Language)的幫助來自動生成這些框架代碼的
AIDL是一種接口描述語言,語法取自Java,在參數上增長了輸入輸出方向的控制。android SDK中提供了AIDL的解析工具,根據所提供的AIDL文件,自動生成對應的MY_API、MY_API.Proxy、MY_API.Stub等類型的Java文件。開發者只須要基礎MY_API.Stub,實現Implement類型,填充真實的執行代碼便可,無需關注其餘的底層通訊細節。
【參數序列化】整個進程間通訊的流程中,爲了將一個進程中的數據傳遞到另外一個進程中,還有一個重要的步驟,就是數據的序列化和反序列化—-這是全部的進程間通訊的基礎。
在Android中,負責序列化和反序列化數據的是Parcel類,它提供了一系列的write和read接口,支持多種類型數據的序列化操做。
支持的數據類型主要有三種
- 基本數據和他們的列表、數組對象。
- 實現Parcelable接口的子類型對象—-子類型經過派生writeToParcel方法和提供構造函數的途徑實現序列化相關的操做。
- IBinder和IInterface的子類型對象也能夠經過Parcel來序列化。
Parcel序列化後的數據是齊位的二進制流。
進程間通訊機制是android的重要基礎,Parcel類型的實現主要經過C++來實現,上層Parcel對象經過JNI接口進行調用,從而提升了序列化相關操做的執行效率,確保Android系統能夠高效運行。
Context.getSystemService接口來得到指定的系統服務,這些系統服務,並非經過服務組件來實現的,他們都位於系統的核心進程中,有獨立的線程空間。
經過Context.getSystemService得到的,實際上是這些服務的代理對象,這些對象會和真正的服務線程創建鏈接,經過IPC調用來實現對應的方法。
觸發器組件Broadcast Receiver解析
所謂觸發器組件,是派生自BroadcastReceiver的子類型。它的實現集中在onReceive方法中。
觸發器組件的功能和特徵
在使用觸發器組件時,只可以把它看成一個函數來使用,除了一些初始化構造時傳入的成員變量,其他都沒有用武之地,不會有機會被用到。功能函數onReceive的執行必須是同步且快速的,不然會阻塞與用戶交互的當前進程。
常見的觸發器組件使用模式
觸發器組件的設計,解決了後臺事件監聽問題。在安卓中,只有當事件真正發生時,組件管理服務纔會根據配置信息通知對應的觸發器組件對象,構造執行組件的進程。
觸發器組件的使用
使用觸發器組件進行時間監聽有兩種方法,分別爲冷拔插和熱拔插。
- 冷拔插,就是將觸發器組件的相關信息寫在應用的配置文件中。
- 熱拔插,經過registerReceiver和unregisterReceiver,動態的將觸發器組件與所須要監聽的時間進行綁定。實際開發中,通常會在activity的onResume函數中進行觸發器組件的註冊,而在onPause函數中註銷對應的觸發器組件。
廣播事件的發送
廣播事件是經過Intent對象來表示,廣播事件須要經過sendBroadcast或sendOrderedBroadcast函數進行發送
經過sendBroadcast的普通廣播模式,這種模式,全部註冊了該廣播事件的觸發器組件都會得到事件通知,併發地在各自的應用進程中執行。
經過sendOrderedBroadcast的有序廣播模式,全部監聽該事件的觸發器組件,都會依照設定的優先級進行排序,從高到低依次處理該事件。高優先級的觸發器組件能夠經過abortBroadcast方法優先終止這個廣播的傳播,這樣,低優先級的觸發器組件就再也不有機會處理該事件了。在有序廣播事件的傳遞過程當中,每一個執行中的觸發器組件均可以經過setResult等函數在該事件消息中附加額外的數據,而下一個處理該事件的觸發器組件則可以使用這些數據。經過這樣的方式,事件廣播來構成一個消息數據處理鏈,爲了保證該事件必定會被處理,廣播事件的發送者還能夠指明默認觸發器組件,若是事件的傳播沒有被提早終止,該觸發器組件會在最後來響應該事件。
數據源組件Content Provider解析
和其餘組件不一樣,數據源組件並不包含特定的功能邏輯,而是負責爲應用提供數據訪問的接口。
數據源組件的定位和操做
數據源組件派生自抽象類ContentProvider,須要實現其中的query、update、insert和delete等抽象接口。數據源組件中數據存儲的方式沒有任何限制,能夠經過數據庫、文件等任意方式來實現
整個數據源組件的接口設計集合了REST標準和數據庫設計的概念,它經過URI(Uniform Resource Identifier)進行定位,像數據庫同樣,經過SQL語句來描述具體的操做
URI,全局統必定位標誌,經過一個結構化的字符串,惟一標誌數據源的地址信息。網絡地址URL,是其中的一個子類,每一個數據源組件都有一個惟一的URI標識
組件開發者須要在配置文件中對應的provider條目下聲明URI信息的地址描述。
<provider android:name="SimpleContentProvider"
android:authorities="com.duguhome.prodiver.sample">
</provider>
數據源組件URI的命名一般要求與所在應用的包名相關聯,以保證在同一個Android設備上具備惟一性。若是在安裝應用的時候,發現設備中已經存在具備相同URI地址的數據源組件,新的應用會因爲數據源組件衝突而安裝失敗。
數據源組件也能夠用REST的方式來定義操做。
# 對列表類型操做
content://com.duguhome.provider.sample/items
# 對id爲1的條目進行操做
content://com.duguhome.provider.sample/items/1
REST是基於HTTP協議而設計的。
爲了增長對數據的控制力,android同時爲數據源組件引入了SQL語句的支持。
數據源組件的開發
從ContentProvider類派生,並實現抽象方法,同時須要在配置文件中描述其URI等信息。
直接對數據源組件進行數據的讀寫操做可能會阻塞主線程,從而影響應用於用戶的交互。當涉及大量的讀寫和查詢時,調用者能夠經過AsyncQueryHandler對象來實現對數據源組件的異步訪問。每一個AsyncQueryHandler對象都會開啓一個後臺線程,在線程中執行與數據源組件的數據交互,進行數據增刪改查操做。
token幫助調用者肯定是那一次請求
數據源組件的實現細節
ContentResolver至關於數據源組件的DNS和本地代理,它負責將各個URI定位到具體的數據源組件,並經由它對數據源進行增刪改查等操做。
ContentResolver對數據的操做,實際上是分兩個步驟完成的,首先是定位,根據URI找到對應的數據源組件,而後,經過對應的數據源組件執行所請求的操做。
有一個數據源組件的緩存對象ProviderMap,它存儲各個URI對應到數據源組件對象。
ContentResolver緩存的數據源組件對象,實際上是對於數據源組件的代理。當ContentResolver調用其接口進行操做時,相關指令打包成消息,經過Android進程間通訊機制傳到遠端的數據源組件中,而數據源組件執行完成後再將結果序列化傳回,整個流程是個同步的操做。
默認狀況下,每一個數據源組件都只有一個實例,來自不一樣進程的要求都會經過進程間通訊機制與其交互,若是頻繁地進行交互則開銷較大,能夠配置參數mutiprocess爲true,此時,數據源組件會在每一個調用它的應用進程中構造一個組件對象,避免進程間通訊的開銷,從而提升操做和數據傳輸的效率
對數據源進程查詢時,數據須要從數據源組件所在的進程中拷貝到調用者所在的進程中。
一次性拷貝浪費時間,浪費內存,每次拷貝一條增長進程間通訊的成本。android採用了數據窗口的模式。在數據指針對象Cursor中,包含一個CursorWindow對象,它會在調用者一端緩存部分數據,緩存數據的內容包含當前指針指向位置相關的若干條數據。CursorWindow類的底層實現是基於C++的
應用配置文件解析
權限配置
權限配置包含權限的定義和權限使用聲明兩部份內容。
應用須要使用的權限,須要經過配置項user-permission來聲明。
若是開發者須要定義權限來自第三方應用的訪問,則經過permission配置項來進行定義。
<
permission
android:name=""
android:label="權限的名字"
android:description="權限的具體描述"
android:permissionGroup=
"android.permission-group.COST_MONEY"
android:protectionLevel="normal"
>
定義了的權限還須要部署到對應的組件才能生效,組件管理服務在構造一個組件對象時,會校驗請求組件的權限聲明是否與該組件的權限配置相匹配(若是請求組件和實現組件位於同一應用,無需進行檢查),若是匹配失敗,會拋出異常阻止此次調用。android的權限體系沒有傳遞性。
權限也能夠不事先部署在組件上,而是在運行時調用checkPermission函數動態校驗。