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

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 oneway keyword 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 Binder thread 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編程語言的基本數據類型 (int, long, char, boolean等),String和CharSequence,集合接口類型List和Map,不須要import 語句。
    而若是須要在AIDL中使用其餘AIDL接口類型,須要import,即便是在相同包結構下。AIDL容許傳遞實現Parcelable接口的類,須要import.
    須要特別注意的是, 對於非基本數據類型,也不是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);時提示空指針異常。觀察了人家的在不一樣程序裏進行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",沒有「:」分號的,則建立全局進程,不一樣的應用程序共享該進程。
 
以上內容結合了很多網絡文章,包括來自
相關文章
相關標籤/搜索