1. 程式人生 > >Android AIDL——實現機制淺析

Android AIDL——實現機制淺析

1.基於前面寫的aidl使用,這段時間準備研究ActivityManager框架,對aidl進行了更深入的研究,因為android框架大量使用了程序通訊機制,所以,在研究android framework前認真研究一下AIDL的實現機制十分有必要的

  2.前面講了aidl是 Android Interface definition language的縮寫,它是一種程序通訊介面的描述,通過sdk直譯器對器進行編譯,會把它編譯成java程式碼在gen目錄下,類路徑與aidl檔案的類路徑相同。

  3.aidl介面
package com.cao.android.demos.binder.aidl; 
import com.cao.android.demos.binder.aidl.AIDLActivity;
interface AIDLService {  
    void registerTestCall(AIDLActivity cb);  
    void invokCallBack();
}

它編譯後生成的java檔案如下

AIDLService.java詳細描述了aidl介面的實現,看上面圖示,AIDLActivity.aidl編譯成了一個介面AIDLActivity,一個存根類Stub,一個代理類Proxy
public interface AIDLService extends android.os.IInterface//與AIDLActivity.aidl中定義的介面對應的java介面實現
public static abstract class Stub extends android.os.Binder implements com.cao.android.demos.binder.aidl.AIDLService
//繼承android.os.Binder,在onTransact完成對通訊資料的接收,通過不同通訊引數code呼叫AIDLService介面方法,並回寫呼叫返回結果AIDLService介面方法需要在
//服務端實現
private static class Proxy implements com.cao.android.demos.binder.aidl.AIDLService
//實現AIDLService介面方法,但是方法只是執行代理遠端呼叫操作,具體方法操作在遠端的Stub存根類中實現

總的來說,AIDLActivity.aidl編譯會生成一個AIDLActivity介面,一個stub存根抽像類,一個proxy代理類,這個實現其實根axis的wsdl檔案編譯生成思路是一致的,
stub存根抽像類需要在服務端實現,proxy代理類被客戶端使用,通過stub,proxy的封裝,遮蔽了程序通訊的細節,對使用者來說就只是一個AIDLActivity介面的呼叫

  4.根據以上思路使用aidl再看一下AIDLService呼叫實現程式碼
--1.在服務端實現AIDLService.Stub抽象類,在服務端onBind方法中返回該實現類
--2.客戶端繫結service時在ServiceConnection.onServiceConnected獲取onBind返回的IBinder物件
        private ServiceConnection mConnection = new ServiceConnection() {
                public void onServiceConnected(ComponentName className, IBinder service) {
                        Log("connect service");
                        mService = AIDLService.Stub.asInterface(service);
                        try {
                                mService.registerTestCall(mCallback);
                        } catch (RemoteException e) {

                        }
                }
        注意mConnection在bindservice作為呼叫引數:bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
--3.AIDLService.Stub.asInterface(service);
public static com.cao.android.demos.binder.aidl.AIDLService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
//如果bindService繫結的是同一程序的service,返回的是服務端Stub物件本省,那麼在客戶端是直接操作Stub物件,並不進行程序通訊了
if (((iin!=null)&&(iin instanceof com.cao.android.demos.binder.aidl.AIDLService))) {
return ((com.cao.android.demos.binder.aidl.AIDLService)iin);
}
//bindService繫結的不是同一程序的service,返回的是代理物件,obj==android.os.BinderProxy物件,被包裝成一個AIDLService.Stub.Proxy代理物件
//不過AIDLService.Stub.Proxy程序間通訊通過android.os.BinderProxy實現
return new com.cao.android.demos.binder.aidl.AIDLService.Stub.Proxy(obj);
}
--4.呼叫AIDLService介面方法,如果是同一程序,AIDLService就是service的Stub物件,等同直接呼叫Stub物件實現的AIDLService介面方法
如果是一個proxy物件,那就是在程序間呼叫了,我們看一個客戶端呼叫的例子:
                        public void onClick(View v) {
                                Log("AIDLTestActivity.btnCallBack");
                                try {
                                        mService.invokCallBack();
                                } catch (RemoteException e) {
                                        // TODO Auto-generated catch block
                                        e.printStackTrace();
                                }
        --mService.invokCallBack()等同呼叫Proxy.invokCallBack,這個時候是程序間呼叫,我們看代理方法的實現
public void invokCallBack() throws android.os.RemoteException
{
//構造一個Parcel物件,該物件可在程序間傳輸
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
//DESCRIPTOR = "com.cao.android.demos.binder.aidl.AIDLService",描述了呼叫哪個Stub物件
_data.writeInterfaceToken(DESCRIPTOR);
//Stub.TRANSACTION_invokCallBack 標識呼叫Stub中哪個介面方法,mRemote在是構造Proxy物件的引數obj,也就是public void onServiceConnected(ComponentName className, IBinder service)
//中的service引數,它是一個BinderProxy物件,負責傳輸程序間資料。
mRemote.transact(Stub.TRANSACTION_invokCallBack, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
--5.BinderProxy.transact 該方法本地化實現
   public native boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
        //對應實現的本地化程式碼 /frameworks/base/core/jni/android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
                                                jint code, jobject dataObj,
                                                jobject replyObj, jint flags)
  //具體程序通訊在c程式碼中如何實現,以後再深入研究。
--6.服務端程序資料接收
        --呼叫堆疊
        ##AIDLService.Stub.onTransact
        ##AIDLService.Stub(Binder).execTransact
        ##NativeStart.run
        --AIDLService.Stub.onTransact
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_registerTestCall:
{
data.enforceInterface(DESCRIPTOR);
com.cao.android.demos.binder.aidl.AIDLActivity _arg0;
_arg0 = com.cao.android.demos.binder.aidl.AIDLActivity.Stub.asInterface(data.readStrongBinder());
this.registerTestCall(_arg0);
reply.writeNoException();
return true;
}
//TRANSACTION_invokCallBack由前面客戶端呼叫的時候transact方法引數決定,code==TRANSACTION_invokCallBack,執行
//invokCallBack方法,方法由繼承Stud的服務端存根類實現。
case TRANSACTION_invokCallBack:
{
data.enforceInterface(DESCRIPTOR);
this.invokCallBack();
reply.writeNoException();
return true;
}

5.裡面設定本地C程式碼的呼叫,我沒有深入研究,隨著後面我對android框架的深入,我會發blog進一步說民底層C程式碼是如何實現程序通訊的,關於AIDL程序通訊,暫時研究到這裡。

原文:http://blog.csdn.net/stonecao/article/details/6579333

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",沒有“:”分號的,則建立全域性程序,不同的應用程式共享該程序。