Android Service學習之AIDL, Parcelable和遠程服務

來源:互聯網
上載者:User

AIDL的作用 
    由於每個應用程式都運行在自己的進程空間,並且可以從應用程式UI運行另一個服務進程,而且經常會在不同的進程間傳遞對象。在Android平台,一 個進程通常不能訪問另一個進程的記憶體空間,所以要想對話,需要將對象分解成作業系統可以理解的基本單元,並且有序的通過進程邊界。
    通過代碼來實現這個資料轉送過程是冗長乏味的,Android提供了AIDL工具來處理這項工作。
     AIDL (Android Interface Definition Language) 是一種IDL 語言,用於產生可以在Android裝置上兩個進程之間進行處理序間通訊(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL產生可序列化的參 數。
    AIDL IPC機制是面向介面的,像COM或Corba一樣,但是更加輕量級。它是使用代理類在用戶端和實現端傳遞資料。
   選擇AIDL的使用場合    官方文檔特別提醒我們何時使用AIDL是必要的: 只有你允許用戶端從不同的應用程式為了進程間的通訊而去訪問你的service,以及想在你的service處理多線程。 
     如果不需要進行不同應用程式間的並發通訊(IPC),you should create your interface by implementing a Binder;或者你想進行IPC,但不需要處理多線程的,則implement your interface using a Messenger。無論如何,在使用AIDL前,必須要理解如何綁定service——bindService。     在設計AIDL介面前,要提醒的是,調用AIDL介面是直接的方法調用的,不是我們所想象的調用是發生線上程裡。而調用(call)來自local進程或者remote進程,有什麼區別呢?尤其是以下情況(引用原文,不作翻譯了,以免翻譯有誤):
  • Calls made from the local process are executed in the same thread that is making the call. If this is your main UI thread, that thread continues to execute in the AIDL interface. If it is another thread, that is the one that executes your code in the service.
    Thus, if only local threads are accessing the service, you can completely control which threads are executing in it (but if that is the case, then you shouldn't be using AIDL at all, but should instead create the interface by implementing
    a Binder ).
  • Calls from a remote process are dispatched from a thread pool the platform maintains inside of your own process. You must be prepared for incoming calls from unknown threads, with multiple calls happening at the same time. In other words, an implementation
    of an AIDL interface must be completely thread-safe.
  • The onewaykeyword modifies the behavior of remote calls. When used, a remote call does not block; it simply sends the transaction
    data and immediately returns. The implementation of the interface eventually receives this as a regular call from the Binderthread
    pool as a normal remote call. If oneway is used with a local call, there is no impact and the call is still synchronous.

定義AIDL介面    AIDL介面檔案,和普通的介面內容沒有什麼特別,只是它的副檔名為.aidl。儲存在src目錄下。如果其他應用程式需要IPC,則那些應用程式的src也要帶有這個檔案。 Android SDK tools就會在gen目錄自動產生一個IBinder介面檔案。service必須適當地實現這個IBinder介面。那麼用戶端程式就能綁定這個service並在IPC時從IBinder調用方法。    每個aidl檔案只能定義一個介面,而且只能是介面的聲明和方法的聲明。  1.建立.aidl檔案 
     AIDL使用簡單的文法來聲明介面,描述其方法以及方法的參數和傳回值。這些參數和傳回值可以是任何類型,甚至是其他AIDL產生的介面。    其中對於Java程式設計語言的基礎資料型別 (Elementary Data Type) (int, long, char, boolean等),String和CharSequence,集合介面類型List和Map,不需要import 語句。    而如果需要在AIDL中使用其他AIDL介面類型,需要import,即使是在相同包結構下。AIDL允許傳遞實現Parcelable介面的類,需要import.
    需要特別注意的是,對於非基礎資料型別 (Elementary Data Type),也不是String和CharSequence類型的,需要有方向指示,包括in、out和inout,in表示由用戶端設定,out表示由服務端設定,inout是兩者均可設定。    AIDL只支援介面方法,不能公開static變數。 
 例如 (IMyService.aidl): 
package com.demo; import com.demo.Person; interface IMyService {         void savePersonInfo(in Person person);         List<Person> getAllPerson(); }

2.實現介面    建立一個類實現剛才那個aidl的介面:
public class RemoteService extends Service {         private LinkedList<Person> personList = new LinkedList<Person>();                  @Override         public IBinder onBind(Intent intent) {                 return mBinder;         }         private final IMyService.Stub mBinder = new IMyService.Stub() {                 @Override                 public void savePersonInfo(Person person) throws RemoteException {                         if (person != null ){                                 personList.add(person);                         }                 }                 @Override                 public List<Person> getAllPerson() throws RemoteException {                         return personList;                 }         }; }

這裡會看到有一個名為IMyService.Stub類,查看aidl檔案產生的Java檔案原始碼就能發現有這麼一段代碼:/** Local-side IPC implementation stub class. */ 
public static abstract class Stub extends android.os.Binder implements com.demo.IMyService    原來Stub類就是繼承於Binder類,也就是說RemoteService類和普通的Service類沒什麼不同,只是所返回的IBinder對象比較特別,是一個實現了AIDL介面的Binder。     接下來就是關於所傳遞的資料Bean——Person類,是一個序列化的類,這裡使用Parcelable 介面來序列化,是Android提供的一個比Serializable 效率更高的序列化類別。    Parcelable需要實現三個函數:
    1)  void writeToParcel(Parcel dest, int flags) 將需要序列化儲存的資料寫入外部提供的Parcel對象dest。而看了網上的代碼例子,個人猜測,讀取Parcel資料的次序要和這裡的write次序一致,否則可能會讀錯資料。具體情況我沒實驗過!
    2)  describeContents() 沒搞懂有什麼用,反正直接返回0也可以
    3)  static final Parcelable.Creator對象CREATOR  這個CREATOR命名是固定的,而它對應的介面有兩個方法:
    createFromParcel(Parcel source) 實現從source建立出JavaBean執行個體的功能

    newArray(int size) 建立一個類型為T,長度為size的數組,僅一句話(return new T[size])即可。估計本方法是供外部類還原序列化本類數組使用。
  仔細觀察Person類的代碼和上面所說的內容:

public class Person implements Parcelable {         private String name;         private String telNumber;         private int age;         public Person() {}         public Person(Parcel pl){                 name = pl.readString();                 telNumber = pl.readString();                 age = pl.readInt();         }         public String getName() {                 return name;         }         public void setName(String name) {                 this .name = name;         }         public String getTelNumber() {                 return telNumber;         }         public void setTelNumber(String telNumber) {                 this .telNumber = telNumber;         }         public int getAge() {                 return age;         }         public void setAge(int age) {                 this .age = age;         }         @Override         public int describeContents() {                 return 0;         }         @Override         public void writeToParcel(Parcel dest, int flags) {                 dest.writeString(name);                 dest.writeString(telNumber);                 dest.writeInt(age);         }         public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {                 @Override                 public Person createFromParcel(Parcel source) {                         return new Person(source);                 }                 @Override                 public Person[] newArray(int size) {                         return new Person[size];                 }         }; }

然後建立Person.aidl檔案,注意這裡的parcelable和原來實現的Parcelable 介面,開頭的字母p一個小寫一個大寫:package com.demo; 

parcelable Person;

      對於實現AIDL介面,官方還提醒我們:    1. 調用者是不能保證在主線程執行的,所以從一調用的開始就需要考慮多執行緒,以及確保安全執行緒;    2. IPC調用是同步的。如果你知道一個IPC服務需要超過幾毫秒的時間才能完成地話,你應該避免在Activity的主線程中調用。也就是IPC調用會掛起應用程式導致介面失去響應,這種情況應該考慮單獨開啟一個線程來處理。
    3. 拋出的異常是不能返回給調用者(跨進程拋異常處理是不可取的)。
 
3. 用戶端擷取介面    用戶端如何擷取AIDL介面呢?通過IMyService.Stub.asInterface(service)來得到IMyService對象:
private IMyService mRemoteService; private ServiceConnection mRemoteConnection = new ServiceConnection() {            public void onServiceConnected(ComponentName className, IBinder service) {                    mRemoteService = IMyService.Stub.asInterface(service);             }            public void onServiceDisconnected(ComponentName className) {                    mRemoteService = null ;            }    }; 在產生的IMyService.java裡面會找到這樣的代碼:/** * Cast an IBinder object into an com.demo.IMyService interface, * generating a proxy if needed. */ public static com.demo.IMyService asInterface(android.os.IBinder obj) {...} 而service的綁定沒有什麼不同:if (mIsRemoteBound) {         unbindService(mRemoteConnection); }else {         bindService(new Intent("com.demo.IMyService" ),                                mRemoteConnection , Context.BIND_AUTO_CREATE); } mIsRemoteBound = !mIsRemoteBound;

通過IPC調用/傳遞資料    用戶端綁定service後就能通過IPC來調用/傳遞資料了,直接調用service對象的介面方法:
addPersonButton.setOnClickListener(                 new View.OnClickListener(){                         private int index = 0;                         @Override                         public void onClick(View view) {                                 Person person = new Person();                                 index = index + 1;                                 person.setName("Person" + index);                                 person.setAge(20);                                 person.setTelNumber("123456" );                                 try {                                         mRemoteService.savePersonInfo(person);                                 } catch (RemoteException e) {                                         e.printStackTrace();                                 }                         }                 }); listPersonButton.setOnClickListener(                 new View.OnClickListener(){                         @Override                         public void onClick(View view) {                                 List<Person> list = null ;                                 try {                                         list = mRemoteService.getAllPerson();                                 } catch (RemoteException e) {                                         e.printStackTrace();                                 }                                 if (list != null ){                                         StringBuilder text = new StringBuilder();                                         for (Person person : list){                                                 text.append("\nPerson name:" );                                                 text.append(person.getName());                                                 text.append("\n             age :" );                                                 text.append(person.getAge());                                                 text.append("\n tel number:" );                                                 text.append(person.getTelNumber());                                         }                                         inputPersonEdit.setText(text);                                 }else {                                         Toast.makeText(ServiceActivity.this , "get data error" ,                                                         Toast.LENGTH_SHORT).show();                                 }                         }                 });

  Permission許可權 
    如果Service在AndroidManifest.xml中聲明了全域的強制的存取權限,其他引用必須聲明許可權才能來start,stop或bind這個service.
     另外,service可以通過許可權來保護她的IPC方法調用,通過調用checkCallingPermission(String)方法來確保可以執行這個操作。  AndroidManifest.xml的Service元素
< service android:name =".RemoteService" android:process =":remote" >         < intent-filter >                 < action android:name ="com.demo.IMyService" />         </ intent-filter > </ service >

這裡的android:process=":remote",一開始我沒有添加的,在同一個程式裡使用IPC,即同一個程式作為用戶端/伺服器端,結果運 行mRemoteService = IMyService.Stub.asInterface(service);時提示null 指標異常。觀察了人家的在不同程式裡進行IPC的代碼,也是沒有這
個android:process=":remote"的。後來在官方文檔http://androidappdocs.appspot.com/guide/topics/manifest/service-element.html 裡瞭解到(留意第二段文字):

android:process
The name of the process where the service is to run. Normally, all components of an application run in the default process created for the application. It has the same name as the application package. The <application> element's process attribute can
set a different default for all components. But component can override the default with its own process attribute, allowing you to spread your application across multiple processes.  If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the service runs in that process .  If the process
name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage.

 也就是說android:process=":remote",代表在應用程式裡,當需要該service時,會自動建立新的進程。而如果是android:process="remote",沒有“:”分號的,則建立全域進程,不同的應用程式共用該進程。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.