android的aidl機制案例
這段時間在學習android,結果碰到了教學視頻中經常提到和使用aidl,隨後在網上找了一些資料,寫了一些案例來分析分析,假如我寫的和解釋的有些偏差,希望能被大家指點出來糾正自己的拙見。AIDL是一個介面描述檔案,用於實現Android平台上面的RPC,aapt在編譯的時候會自動根據規則產生用於IPC的介面和對象,而作為使用者只需要:1.在服務端Service實現介面;2. 在用戶端bindService,onServiceConnected時擷取介面對象。這裡的介面都是AIDL中描述的介面,其他的細節則在由AIDL產生的同名源碼檔案中
我所實現的案例是從服務端擷取一個書本的資訊:
package com.edify.bluenfcserver.myservice;import java.util.Date;import android.annotation.SuppressLint;import android.os.Parcel;import android.os.Parcelable;/** * @Description 寫一個實體類步驟: * 1. 實現Pracelable介面 * 2. 需要定義一個Parcelable.Creator CREATOR對象 * 3. 同時要建立一個aidl檔案 * @Author monkey * @Date 2014年9月4日下午5:22:02 */@SuppressLint("ParcelCreator")public class BookInfo implements Parcelable {private int id;private String name;private String author;private float price;private Date product;public BookInfo(Parcel in) {}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Date getProduct() {return product;}public void setProduct(Date product) {this.product = product;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public BookInfo(int id, String name, String author, float price, Date product) {super();this.id = id;this.name = name;this.author = author;this.price = price;this.product = product;}@Overridepublic int describeContents() {return 0;}/** * 將對象序號 * dest 就是對象即將寫入的目的對象 * flags 有關對象序號的方式的標識 * 這裡要注意,寫入的順序要和在createFromParcel方法中讀出的順序完全相同。例如這裡先寫入的為name, * 那麼在createFromParcel就要先讀name */@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(id);dest.writeString(name);dest.writeString(author);dest.writeFloat(price);dest.writeValue(product);// 寫一個date類型的資料}/** * 在想要進行序號傳遞的實體類內部一定要聲明該常量。常量名只能是CREATOR,類型也必須是 * Parcelable.Creator */public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {/*** * 根據序號的Parcel對象,反序號為原本的實體物件 * 讀出順序要和writeToParcel的寫入順序相同 */public BookInfo createFromParcel(Parcel in) {int id = in.readInt();String name = in.readString();String author = in.readString();float price = in.readFloat();Date product = (Date) in.readValue(this.getClass().getClassLoader());return new BookInfo(id, name, author, price, product);}public BookInfo[] newArray(int size) {return new BookInfo[size];}};}
用戶端的代碼:
package com.edify.bluenfc;import android.app.Activity;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import com.edify.bluenfcserver.Beauty;import com.edify.bluenfcserver.RemoteBeauty;import com.edify.bluenfcserver.myservice.BookInfo;import com.edify.bluenfcserver.myservice.MyRemoteBookInfo;import com.edify.bluenfcserver.myservice.MyRemoteBookInfo.Stub;public class MainActivity extends Activity implements OnClickListener {TextView textView;Button button;String actionName = "com.edify.bluenfcserver.remoteservice";String actionName2 = "com.edify.bluenfcserver.myservice.myRemoteService";RemoteBeauty remoteBeauty;private MyRemoteBookInfo myRemoteBookInfo;private TextView textView2;private View button2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = (TextView) findViewById(R.id.textView1);textView2 = (TextView) findViewById(R.id.textView2);button = (Button) findViewById(R.id.button1);button2 = (Button) findViewById(R.id.button2);button.setOnClickListener(this);button2.setOnClickListener(this);}// service.queryLocalInterface("com.edify.bluenfcserver.RemoteBeauty");// getPackageManager():提供了方法可以對資訊清單檔中的配置進行查詢private class MyServiceConnection implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i("通知", "連結成功!");remoteBeauty = RemoteBeauty.Stub.asInterface(service);try {Beauty beauty = remoteBeauty.getBeauty();textView.setText("美女 姓名:" + beauty.getName() + " 年齡:" + beauty.getAge() + " 性別:" + beauty.getSex());} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}}MyServiceConnection connection = new MyServiceConnection();@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.button1:Intent intent = new Intent(actionName);bindService(intent, connection, Context.BIND_AUTO_CREATE);break;case R.id.button2:Intent intent2 = new Intent(actionName2);// 建立串連:第一步,向服務端發請求bindService(intent2, new MyBookInfoServiceConnection(), Context.BIND_AUTO_CREATE);break;}}// 建立與伺服器連結化物件,橋樑private class MyBookInfoServiceConnection implements ServiceConnection {// 建立串連:第三步@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 判斷是否是同一個進程內的進行通訊,假如是那麼返回的是同一個對象,如果不是那麼返回的是代理對象/** * 假如是一個進程內通訊,就沒有必要進行aidl的機制來處理了 * 這實際上就是判斷此通訊是在同一進程中,還是跨進程,因為同一進程傳回的對象是Service * .onBind()所返回的對象,而此對象必然實現了介面 * (要不然搞毛啊!)。所以,如果僅是在同一個進程之中,不會走Binder進程IPC * ,而是直接返回Service所提供的對象,直接調用其方法,因此也就不會有對象必須Parcelable的限制! * 也就是說,當在同一個進程中時AIDL實際上變成了這樣的: * 也就是說它是直接返回了Service.onBind()的對象,這其實跟前面提到的第一種方式 * :直接實現Binder對象的方式是一樣一樣的 * ,其他的代碼全是多餘的。因此,如前面建議的,如果僅是在同一個進程中,就直接使用Binder就好了,沒有必要建立AIDL檔案。 * 當在不同的進程中時 * ,用戶端Stub.asInterface會返回一個Stub.Proxy對象,調用其上的print方法。而服務端僅會執行Stub * .onTransact()方法,然後就調到Service的print方法了。 * 當跨進程的時候,就要使用Binder對象的IPC相關的方法和機制 * 。用戶端需要實現Binder.transact()方法來執行遠端一個方法 * ,這是給用戶端來使用;而服務端則需要實現Binder.onTransact * ()來響應用戶端所請求的方法。對於上層使用者來說,用transact * ()把函數的資訊(參數,標識和開關)發送出去,剩下的就是Binder的工作了 * ,內部還有大量的細節,但是最終會調用到服務端Binder的onTransact * ()方法,這裡識別出函數的標識,然後調用具體的實現,再傳回傳回值,這樣一個IPC的函數調用就完成了。 * 當跨進程時,僅以下代碼是各自所必須的,去掉了無關代碼: * * 其實AIDL的作用就是對Binder的二個方法:Binder.transact()和Binder.onTransact * ()進行封裝, * 以供Client端和Server端進行使用。因為實現transact()和onTransact()方法的方式基本上是相同的 * ,所以就可以用模板來產生具體的代碼 * 。理論上講只需要為Client端產生transact()相關代碼,為服務端產生onTransact * ()代碼即可,但因為工具無法準確的確定某一個應用到底是Client端還是Server端 * ,所以它就產生所有的代碼,放在一個檔案中。這就是你看到的自動產生的檔案。 * 還需要注意的一點是Client端的Proxy是組合Binder對象 * ,調用其transact()方法;而服務端必須繼承Binder對象 * ,覆寫onTransact()方法。為蝦米呢?因為Client是主動發起IPC函數Call * ,所以它可以直接調用Binder的方法來進行IPC。而Server是被動的,是要接收進來的IPC * call,但Service自己無法得知啥時候Call會來 * ,因此必須實現回調(onTransact())給Binder,以讓Binder在有IPC Call進來的時候告訴Service。 * 原理和內幕 AIDL的角色是實現Android平台上面的RPC(Remote Procedure * Call)也即遠程常式調用。RPC是IPC中的一種,但是它是以調用在本地或者另一個進程,甚至是另一個主機上的方法的機制。 * RPC的目的就是可以讓程式不用擔心方法具體是在哪個進程裡面或者哪以機器上面 * ,就像正常的本地方法那樣去調用即可,RPC機制會處理所有的具體細節。RPC一般用IDL(Interface Definition * Language)來描述,實現則要看具體的平台和語言。可以參考Wikipedia來看RPC 和IDL 的更多資訊。 * AIDL提供Android平台的RPC的支援:開發人員僅需要要定義AIDL,做一些相關的適配工作,然後就可以使用這些方法了, * 不用具體關心介面描述的方法空究竟是在同一個進程中還是在其他的進程中。這些RPC實現的細節由Binder和系統來處理。 */myRemoteBookInfo = MyRemoteBookInfo.Stub.asInterface(service);try {// 建立串連:第四步到服務端請求資料,@Override public BookInfo// getBookInfo(java.lang.String tagName)// 調用了這個方法去請求資料:mRemote.transact(Stub.TRANSACTION_getBookInfo,// _data, _reply, 0);BookInfo bookInfo = myRemoteBookInfo.getBookInfo("我是一個tagName");textView2.setText(bookInfo.toString());} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}}}
服務端代碼:
package com.edify.bluenfcserver.myservice;import java.util.Date;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;public class MyRemoteService extends Service {//建立串連:第二步,返回一個該提供給用戶端操作服務端的對象@Overridepublic IBinder onBind(Intent intent) {return stub;}/** * 執行個體化一個對象,該對象就是提供給用戶端的對象 */private MyRemoteBookInfo.Stub stub = new MyRemoteBookInfo.Stub() {/** *實現介面未實現的方法 *///用戶端請求是通過這個方法來判斷具體請求那個操作@Override public boolean onTransact(int code, android.os.Parcel data, an//建立串連:第五步;通過調用該方法回去伺服器端的資料資訊,操作@Overridepublic BookInfo getBookInfo(String tagName) throws RemoteException {return getBookInfoByTagName(tagName);}};/** * @Description 服務的自身的方法 * @Author monkey * @param tagName * @return * @Return BookInfo */public BookInfo getBookInfoByTagName(String tagName){Log.i("getBookInfoByTagName--------------", tagName);if(tagName != null){return new BookInfo(1, "盡忠報國", "金庸", 100f, new Date());}else{return new BookInfo(1, "水滸傳", "未知", 200f, new Date());}}}