天天看點

Service與Android系統實作(1)

共18次連載,講述Android Service背後的實作原理,透析Binder相關的RPC。

Service 在Android應用程式裡四大實體之一。Android的應用程式不光是需要有圖形界面來進行互動,有時也會需要在沒有互動的情況下進行的操作,比如下 載、更新、監聽等。比如目前對我們網絡生存影響如此之大的社交網絡、或是更老一些聊天工具,總需要這類應用程式可以一直在背景運作,以等待可能過來的消 息。即使我們寫一些非常簡單的基于GPS來記錄自己位址的一些小應用程式,我們可能都會有這種需求:

這 個簡單的小應用程式,幾乎涉及到Android應用程式的四大元件:Activity、Service、BroadcastReceiver、 Content Provider。在這一應用程式裡建立了一個BootReceiver的BroadcastReceiver,用于監聽是否有啟動完成的消息過來,進而 實作開機自動啟動。這Boot Receiver此時是不應該觸發Activity的,它隻會啟動一個Tracker Service,在後來開始監聽GPS狀态,或是在某些合适的時間點,比如提示使用者到了某個地點時,打開一個Activity進行提示。

從 這個簡單的應用程式設計裡,可以看出Service所能完成,但Activity又做不到的事情。一是背景運作,有時我們并不希望有過多對話框來影響使用者 體驗,開機自動啟動,便可默默地在背景運作。另一特性,就是不被Activity生命周期所管理,Activity處于完全活躍的周期是 onResume()與onPause()之間,如果這周期之外發生了事件,實際上Activity構成的執行部分也不會被執行到,進而無法響應處理,但 Service由于本身過于簡單,則會通過一定的輔助手段來達到這個目标。如果對編寫惡意軟體或是安全軟體感興趣,則Service是必然的程式設計選擇,因 為Service這種在背景執行,不受限于互動的特性。

Activity對應用程式來說是最重要的元件,但從Android系統設計的角度 來看,Service對系統層實作來說才最重要的。Service是建構系統的根本,支援整個系統營運的環境framework,本身就是由大量 Service來構成的。也就是說,Service反而是建構Activity的基礎環境。

Android與其他系統設計最大的不同之處在 于,它并不是一種傳統的系統環境,而是一種更加開放的系統。傳統的圖形作業系統裡,會有基本環境,會有系統管理元件,應用程式隻是作為補充性功能實作。但 Android并不如此,Android系統是沒有應用程式的,達到了“無邊界”系統設計的最高境界,“手裡無劍,心中有劍”。整個Android系統的 設計使自己扮演着支撐系統的運作環境的角色,不再有基本系統的概念,而變成了一種“有或者無”的應用程式的支撐環境,沒有系統元件的概念。而我們所謂的系 統應用程式,我們隻能稱它們為“内置”應用程式,隻是服務于黑心移動營運商的一種方式而已。

這種設計的精髓在于,系統本身不會處理互動,而 隻是提供互動的手段。從前面我們對于應用程式運作環境的分析中,我們可以看到,Android的Framework,提供一部分功能供應用程式調用,而除 了這些應用程式直接使用的API實作,其他代碼邏輯就會全是由Service構成。當然作為系統實作角度的Service,與應用程式程式設計裡實作的 Service是有差别的,更強調共享,但基本構架一樣。在過渡到Android系統的解析之前,我們先從應用程式的Service概念入手。

我 們先來在應用程式裡寫一個簡單的Service。打開Eclipse,建立一個Android工程,然後再建立一個新的基于Service基類的類。與 Activity的程式設計方式類似,Service在程式設計上也是基于回調方式實作的,我們繼承基類Service之後所需要做的,就是通過IoC模式替換原 來的Service回調的實作:

import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class LianlabServiceextends Service {     private staticfinal String TAG ="LianlabService";    @Override     public void onCreate() {        super.onCreate();        Log.v(TAG, "inonCreate()");     }     public int onStartCommand(Intent intent,int flags,int startId) {        super.onStartCommand(intent, flags, startId);        Log.v(TAG, "inonStartCommand()");        return START_STICKY;     public void onDestroy()     {        Log.v(TAG, "inonDestroy().");        super.onDestroy(); }

有了Service的具體實作之後,系統并不會自動地識别到這一實作,在Android世界裡,一切都通過AndroidManifest.xml來驅動,于是,我們還需要修改AndroidManifest.xml檔案,加入Service的定義:

    <applicationandroid:label="@string/app_name"> …        <serviceandroid:name=".LianLabService"/>  </application> 

在 上面這種方式裡實作的Service,可被執行的方式很有限,就是提供一個可執行的線程環境,可以被Intent所驅動,執行 onStartCommand()回調。功能有限并不代表無能,在Android系統裡,我們可能還經常會碰到這樣的需求:比如我們使用GPS裡來記錄我 們行動軌迹時,這時我們很可能需要通過背景的執行的代碼來定時檢查GPS的定位資訊;殺毒或是監控軟體可能希望駐留在背景,并可被Intent來驅動開始 進行殺毒;我們的聊天或是社交應用,需要在背景定時地與服務發送“心跳”(Heart beat),用來辨別自己的線上狀态等。這樣的例子,大家可以回頭到我們畫的GPS軌迹跟蹤的構成示意圖,這樣的跟蹤軟體,必須是通過一個接收啟動完成信 息的Broadcast Receiver來監聽自己是否應該被執行,而接收到到啟動完成的Broadcast Intent之後,則必須觸發一直在背景運作的TrackerService的執行。

既然我們在上述方式裡實作的Service是由 Intent驅動的,于是我們的使用這一Service部分的代碼也會很簡單。在任何可被執行到的代碼裡使用startService(Intent)就 可以完成,我們可以給某個控件注冊點選事件支援onClickListener對象,然後覆寫onClick()回調方法:

    <b>public void</b> onClick(Viewv) {

         Intent intent = <b>new</b> Intent(<b>this</b>,

         LianlabService.<b>class</b>);

       startService(intent);

   }  

我 們這裡既然使用到了Intent,也就是說我們還可以通過extras這個Bundle對象給我們這裡實作的LianLabService來傳遞運作的參 數。于是,這時我們的代碼貌似有了pthread多線程執行效果,通過傳參,然後我們會執行一個在另一線程裡運作的函數,隻是函數是固定的 onStartCommand()回調方法。但這隻是貌似,并非實際情況,Service的執行與背景線程方式極大不同,Service隻是一種代碼邏輯 的抽象,實際上它還是運作在Activity同一線程上下文環境。

于是,我們并不能用Service來進行任何耗時操作,否則會阻塞主線程而造成應用程式的無法響應錯誤,也就是臭名昭著的ANR錯誤。Service僅能用于不需要界面互動的代碼邏輯。

這 種使用Intent來驅動執行的Service,可用性有限,并不能完全滿足我們對于背景服務的需求。對于背景執行的代碼,我們更多的應用情境不光是希望 進行背景操作,我們可能還希望能進行互動,可以随時檢查背景操作的結果,并能暫停或是重新開機背景執行的服務,可以在使用某一Service時保證它并不會退 出執行,甚至一些送出一些參數到背景來進行複雜的處理。這時,我們可以使用Service的另一個通路方式,使用Binder接口來通路。我們的 Service基類還提供這類應用的回調方式,onBind()、onUnbind()和onRebind()。使用Binder來通路Service的 方式比Intent驅動的應用情境更底層,onBind()回調主要用于傳回一個IBinder對象,而這一IBinder對象是Service的引用, 将會被調用端用于直接調用這一Service裡實作的某些方法。

同樣的Service實作,如果通過IBinder來驅動,則會變成下面的樣子:

    public intonStartCommand(Intent intent,int flags,int startId) {        Log.v(TAG, "in onStartCommand()");       super.onDestroy();    <b>final</b>IService.Stub m_binder =<b>new</b>IService.Stub() {         ...    }     public IBinderonBind(Intent intent) {        Log.v(TAG, "inonBind().");        return mBinder;     public booleanonUnbind(Intent intent) {        Log.v(TAG, "inonUnbind().");              return mAllowRebind;     public void onRebind(Intentintent) {        Log.v(TAG, "inonRebind().");

使 用IBinder對象來觸發的Service,在通路時的代碼實作則變得完全不樣了。比如我們同樣通過onClick()來操作背景的某些操作,但這時并 非通過Intent來完成,而是直接使用某個引用這一Service的IBinder對象來直接調用Service裡實作的方法。

       bindService(intent, m_connection, …);

       <b>private</b> ServiceConnection m_connection =<b>new</b> ServiceConnection() {

           <b>private</b> IService onServiceConnected(…, IBinder service) {

                m_service =IService.Stub.asInterface(service);

            }

       }

如 果Service裡實作了某些方法,比如kill(),在上述代碼之後,我們對Service的驅動則會變成代碼上的直接調用。在 onServiceConnected()回調方法被觸發之後,我們始終都可以通過m_service.kill()來通路Service裡的 kill()方法。而bindService()這方法的調用,則會觸發onServiceConnected()事件。

       這樣就要讓人抓狂了,既然如此麻煩,何不直接調用呢?是以,事實上,這裡列舉的這種代碼實作方式,在現實程式設計裡确實不常用。一般而言,如果Service 通過IBinder對象來觸發,那隻會出于一個理由,提供一種可能性,将來可以更靈活地提供給另一程序來通路,這就是我們稍後會說明的Remote Service。

這兩種不同的Service的實作方式,将決定Service的不同被調用方式,或者準确地說,将決定Service的不同生命周期。

如 圖所示,Service的程序存活期是處理onCreate()與onDestroy()回調方法之間。onStartCommand()回調是不受控 的,每次Intent都将觸發執行一次, onStartCommand()執行完則會退出;而使用onBind()來驅動的Service,其活躍區間是onBind()與onUnbind() 之間,其活躍周期始終在控制範圍内。

我們可以結合傳統的RPC概念,并透過IBinder的RPC支援來看Android的跨程序調用。出于“沙盒”式的系統設計,Android系統更依賴于跨程序的互動。

如我們前面所說,如果隻是提供背景服務,我們一般不會無事找抽地來通過IBinder來通路Service,那這種方式存在的意義何在?IBinder在我們後面的内容裡會進一步說明,在這裡,我們可認為Binder就是一種系統的IPC機制,可以在程序間傳遞資料。

       Binder所能完成的作用僅是IPC,雖然Binder本身具有強大的面向對象能力,可以在兩個程序間傳遞對象,但通過Binder得到的,實際上還是 兩個程序空間裡的對象。也就是說,這時一個程序裡修改了對象屬性,并非可以改變另一個程序的對象,因為這時兩個程序分别擁有自己獨立的程序空間。如果需要 建構于IPC機制上的互相通信,這時還需要通過對象引用能通路到對象的公開出來的方法,并非可以通過對象的Getter/Setter方法來修改對象的屬 性(出于面向對象的代碼規範性,一般不直接修改對象屬性,而通過getter/setter類型的方法來修改)。

       出于這種更加具有互動性的跨程序通路,實際上并非Android環境才需要,這是所有跨程序軟體設計裡的必須項。這種互動性的跨程序需求,跟我們傳統的 C/S(用戶端/伺服器)構架類似,用戶端使用IPC通路服務,而伺服器端則實作具體的代碼邏輯,通過IPC提供服務。唯一的差別是受限于調用時的行為模 式,一般,跨程序互動隻提供串行通路。下面是一個典型的跨程序互動的實作:

       程序1提供用戶端功能,而程序2提供伺服器功能,在程序1裡調用RPCFunc(1,2),實際上會觸發到程序2裡的 RPCFunc1Impl1(1,2)的執行。需要通過IPC機制在底層把這樣的通路實作出來,這樣在用戶端程序空間裡可以找到 RPCFunc1Stub()的定義,用于将函數調用解析為基于IPC的請求消息,而在伺服器端則會RPCFunc1Skel()來監聽所有的請求,然後 再具體調用請求轉發到自己實作的RPCFunc1Impl()。當RPCFunc1Impl()執行完成之後,所傳回的值則會經由IPC,再傳回給客戶 端,然後在用戶端RPCFunc1()的return語言裡傳回。這樣,從程序1的代碼上來看,好像是完成了一次從RPCFunc1()到 RPCFunc1Impl()的遠端過程調用(RPC),但在内容實作上,則是經曆了1-6這樣6個步驟的串行操作。之說以說是串行,因為這6個步驟會順 序進行,程序1的執行RPCFunc1()這一函數時,直到第6步執行完成之前,都會被阻塞住。

通過通過串行實作後的這種特殊C/S架構, 因為跟我們傳遞的函數調用類似,隻是提供了跨程序的函數調用,于是根據這樣的行為特征,我們一般會叫它們為遠端過程調用(Remote Procedure Call,簡稱RPC)。支撐起RPC環境的是IPC通信機制,我們也知道套接字(Socket)也是IPC機制一種,而TCP/IP是Socket機制 的一部分,于是很自然的,RPC也天生具備跨網絡的能力,在實際的部署裡,RPC一般會是網絡透明的,通過RPC來進行通路的那端,并不會知道具體實作 RPC的會是本地資源或是網絡上的資源。RPC是實作複雜功能的基礎,特别是一些分布式系統設計裡,比如我們Linux環境裡的網絡檔案系統NFS、 AFS等,都是基于RPC,還有更進階一點,像我們的Corba環境、J2EE環境和WebService,都會使用RPC的變種。

擁有了 RPC通信能力之後,我們在程式設計上的局限性便大為減小,我們的代碼可以很靈活地通過多程序的方式進行更安全的重構,而且還可以進行伸縮度非常良好的部署。 這點對于Android來說,尤為重要,因為我們的Android系統就是建構在基于多程序的“沙盒”模型之上的。在Android環境裡,不存在基于網 絡來進行RPC的需求(也不是沒有,實際上Android有很多應用程式是基于社交網絡API,或是Web Service來建構的,隻是Android的基礎系統是不需要進行跨網絡互動),而是使用高性能的面向對象式的Binder,于是,我們的RPC,需要 通過RPC來建構。于是,簡單的RPC通信流程,在Android系統裡,則可以通過IBinder對象引用來完成,得到如下的實作邏輯:

我 們會通過IBinder,來實作從一個程序來通路另一個程序的對象,得到遠端對象的引用之後,雖然在程序1裡我們像是真正通過這一IBinder來通路遠 程對象的某些方法,比如doXXX()方法,但實際上後續的執行邏輯則會被轉到Binder IPC來發送通路程序,程序1在進入doXXX()方法之後,就會進入IBinder是否有傳回的檢測循環。當然此時由于IBinder設計上的精巧性, 此時程序實際上會休眠到/dev/binder裝置的休眠隊列裡。而提供RPC的程序2則相反,它啟動後會一直在IPC請求上進行循環監聽,當有IPC請 求過來之後,則會将doXXX()的通路請求解析出來,通路這一方法,在通路完成之後,再将調用doXXX()的結果通過IBinder傳回給發出調用請 求的程序1。這時,會喚醒程序1繼續往下執行,從IPC上取回調用的傳回值,然後再執行doXXX()之後的代碼。

通過這種RPC機制,在 Android系統裡,就可以更靈活地來設計互動過程,更友善地在多程序環境裡進行低耦合化設計。在RPC互動的Server端的實作,可以靈活地根據自 己的實作或是調用上的需求,開放出來一部分的自己實作的接口,進而給多個Client端提供服務。

這些具體實作功能的部分,可以被稱為 Server,但Server則容易讓人聯想到它将具備網絡通信的能力,于是這種基于Binder能夠提供RPC被調用能力的實作部分,在Android 裡會被稱為Service(在Android世界裡,基本的功能元件的命名,Activity不等同于Frame、Windows,是以叫 Activity,Service也不等同于Server,于是被稱為Service)。而根據Service是否提供RPC能力(方法級調用的能力), 又會被區分為本地Service與Remote Service。本地Service是基于Intent的,也可能在跨程序環境裡被調用,但是在這種調用模型裡,Service對象本身隻在本地存在,不 會跨程序。

于是,Android裡支援這種程序間的互相調用,剩下的問題就是支援基于Binder的這種RPC通信。見AIDL的設計

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/1031110,如需轉載請自行聯系原作者

繼續閱讀