android service詳解

鴨脖發表於2012-08-24

服務--Service


Android中的服務和windows中的服務是類似的東西,服務一般沒有使用者操作介面,它執行於系統中不容易被使用者發覺,可以使用它開發如監控之類的程式。服務的開發比較簡單,如下:
第一步:繼承Service類
public class SMSService extends Service { }

第二步:在AndroidManifest.xml檔案中的<application>節點裡對服務進行配置:
<service android:name=".SMSService" />

服務不能自己執行,需要通過呼叫Context.startService()或Context.bindService()方法啟動服務。這兩個方法都可以啟動Service,但是它們的使用場合有所不同。使用startService()方法啟用服務,訪問者與服務之間沒有關連,即使訪問者退出了,服務仍然執行。使用bindService()方法啟用服務,訪問者與服務繫結在了一起,訪問者一旦退出,服務也就終止,大有“不求同時生,必須同時死”的特點。

採用Context.startService()方法啟動服務,只能呼叫Context.stopService()方法結束服務,服務結束時會呼叫onDestroy()方法。


兩種方式啟動服務的區別:


通過startService()和stopService()啟動關閉服務。適用於服務和訪問者之間沒有互動的情況。如果服務和訪問者之間需要方法呼叫或者傳遞引數,側需要使用bindService()和unbindService()方法啟動關閉服務。

採用Context.bindService()方法啟動服務,在服務未被建立時,系統會先呼叫服務的onCreate()方法,接著呼叫onBind()方法,這個時候訪問者和服務繫結在一起。 如果訪問者要與服務進行通訊,那麼,onBind()方法必須返回Ibinder物件。如果訪問者退出了,系統就會先呼叫服務的onUnbind()方法,接著呼叫onDestroy()方法。如果呼叫bindService()方法前服務已經被繫結,多次呼叫bindService()方法並不會導致多次建立服務及繫結(也就是說onCreate()和onBind()方法並不會被多次呼叫)。如果訪問者希望與正在繫結的服務解除繫結,可以呼叫unbindService()方法,呼叫該方法也會導致系統呼叫服務的onUnbind()-->onDestroy()方法。

服務的生命週期回撥方法
:

服務的生命週期跟啟動服務的方法有關:
 當採用Context.startService()方法啟動服務,與之有關的生命週期方法
onCreate()--> onStart()--> onDestroy()
onCreate()該方法在服務被建立時呼叫,該方法只會被呼叫一次,無論呼叫多少次startService()或bindService()方法,服務也只被建立一次。
onStart() 只有採用Context.startService()方法啟動服務時才會回撥該方法。該方法在服務開始執行時被呼叫。多次呼叫startService()方法儘管不會多次建立服務,但onStart() 方法會被多次呼叫。
onDestroy()該方法在服務被終止時呼叫。

 當採用Context.bindService()方法啟動服務,與之有關的生命週期方法
onCreate()--> onBind() --> onUnbind() --> onDestroy()
onBind()只有採用Context.bindService()方法啟動服務時才會回撥該方法。該方法在呼叫者與服務繫結時被呼叫,當呼叫者與服務已經繫結,多次呼叫Context.bindService()方法並不會導致該方法被多次呼叫。
onUnbind()只有採用Context.bindService()方法啟動服務時才會回撥該方法。該方法在呼叫者與服務解除繫結時被呼叫。

如果先採用startService()方法啟動服務,然後呼叫bindService()方法繫結到服務,再呼叫unbindService()方法解除繫結,最後呼叫bindService()方法再次繫結到服務,觸發的生命週期方法如下:
onCreate()-->onStart()-->onBind()-->onUnbind()[過載後的方法需返回true-->onRebind()


使用AIDL和遠端服務實現程式通訊


在Android中, 每個應用程式都有自己的程式,當需要在不同的程式之間傳遞物件時,該如何實現呢? 顯然, Java中是不支援跨程式記憶體共享的。因此要傳遞物件, 需要把物件解析成作業系統能夠理解的資料格式, 以達到跨界物件訪問的目的。在JavaEE中,採用RMI通過序列化傳遞物件。在Android中, 則採用AIDL(Android Interface Definition Language:介面定義語言)方式實現。

    AIDL是一種介面定義語言,用於約束兩個程式間的通訊規則,供編譯器生成程式碼,實現Android裝置上的兩個程式間通訊(IPC)。AIDL的IPC機制和EJB所採用的CORBA很類似,程式之間的通訊資訊,首先會被轉換成AIDL協議訊息,然後傳送給對方,對方收到AIDL協議訊息後再轉換成相應的物件。由於程式之間的通訊資訊需要雙向轉換,所以android採用代理類在背後實現了資訊的雙向轉換,代理類由android編譯器生成,對開發人員來說是透明的。

實現程式通訊,一般需要下面四個步驟:

假設A應用需要與B應用進行通訊,呼叫B應用中的download(String path)方法,B應用以Service方式向A應用提供服務。需要下面四個步驟: 

1> 在B應用中建立*.aidl檔案,aidl檔案的定義和介面的定義很相類,如:在cn.itcast.aidl包下建立IDownloadService.aidl檔案,內容如下:
package cn.itcast.aidl;
interface IDownloadService {
    void download(String path);
}
當完成aidl檔案建立後,eclipse會自動在專案的gen目錄中同步生成IDownloadService.java介面檔案。介面檔案中生成一個Stub的抽象類,裡面包括aidl定義的方法,還包括一些其它輔助方法。值得關注的是asInterface(IBinder iBinder),它返回介面型別的例項,對於遠端服務呼叫,遠端服務返回給客戶端的物件為代理物件,客戶端在onServiceConnected(ComponentName name, IBinder service)方法引用該物件時不能直接強轉成介面型別的例項,而應該使用asInterface(IBinder iBinder)進行型別轉換。

編寫Aidl檔案時,需要注意下面幾點: 
  1.介面名和aidl檔名相同。
  2.介面和方法前不用加訪問許可權修飾符public,private,protected等,也不能用final,static。
  3.Aidl預設支援的型別包話java基本型別(int、long、boolean等)和(String、List、Map、CharSequence),使用這些型別時不需要import宣告。對於List和Map中的元素型別必須是Aidl支援的型別。如果使用自定義型別作為引數或返回值,自定義型別必須實現Parcelable介面。
  4.自定義型別和AIDL生成的其它介面型別在aidl描述檔案中,應該顯式import,即便在該類和定義的包在同一個包中。
  5.在aidl檔案中所有非Java基本型別引數必須加上in、out、inout標記,以指明引數是輸入引數、輸出引數還是輸入輸出引數。
  6.Java原始型別預設的標記為in,不能為其它標記。

2> 在B應用中實現aidl檔案生成的介面(本例是IDownloadService),但並非直接實現介面,而是通過繼承介面的Stub來實現(Stub抽象類內部實現了aidl介面),並且實現介面方法的程式碼。內容如下:
public class ServiceBinder extends IDownloadService.Stub {
    @Override
    public void download(String path) throws RemoteException {
        Log.i("DownloadService", path);
    }        
}

3> 在B應用中建立一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl介面的物件(本例是ServiceBinder)。內容如下:
public class DownloadService extends Service {
    private ServiceBinder serviceBinder = new ServiceBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return serviceBinder;
    }
    public class ServiceBinder extends IDownloadService.Stub {
        @Override
        public void download(String path) throws RemoteException {
            Log.i("DownloadService", path);
        }        
    }
}
其他應用可以通過隱式意圖訪問服務,意圖的動作可以自定義,AndroidManifest.xml配置程式碼如下:
<service android:name=".DownloadService" >
    <intent-filter>
        <action android:name="cn.itcast.process.aidl.DownloadService" />
    </intent-filter>
</service>

4> 把B應用中aidl檔案所在package連同aidl檔案一起拷貝到客戶端A應用,eclipse會自動在A應用的gen目錄中為aidl檔案同步生成IDownloadService.java介面檔案,接下來就可以在A應用中實現與B應用通訊,程式碼如下:
public class ClientActivity extends Activity {
    private IDownloadService downloadService;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        this.bindService(new Intent("cn.itcast.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//繫結到服務
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unbindService(serviceConnection);//解除服務
    }    
    
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadService = IDownloadService.Stub.asInterface(service);
            try {
                downloadService.download("http://www.itcast.cn");
            } catch (RemoteException e) {
                Log.e("ClientActivity", e.toString());
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            downloadService = null;
        }
    };
}

程式間傳遞自定義型別引數

Aidl預設支援的型別包話java基本型別(int、long、boolean等)和(String、List、Map、CharSequence),如果要傳遞自定義的型別該如何實現呢?

要傳遞自定義型別,首先要讓自定義型別支援parcelable協議,實現步驟如下:
1>自定義型別必須實現Parcelable介面,並且實現Parcelable介面的public void writeToParcel(Parcel dest, int flags)方法 。
2>自定義型別中必須含有一個名稱為CREATOR的靜態成員,該成員物件要求實現Parcelable.Creator介面及其方法。

3> 建立一個aidl檔案宣告你的自定義型別。


Parcelable介面的作用:實現了Parcelable介面的例項可以將自身的狀態資訊(狀態資訊通常指的是各成員變數的值)寫入Parcel,也可以從Parcel中恢復其狀態。 Parcel用來完成資料的序列化傳遞。



Parcelable介面與Serailzable介面區別:

Serailzable介面為 jdk中定義,Parcelable介面為GOOGLE在android sdk中定義

都是用來實現資料的可序列化

序列化的方式不同


相關文章