Android IPC機制(二):AIDL的基本使用方法
一、前言
上一篇文章,講述了實現序列化和反序列化的基本方式,是實現進程間通訊的必要條件,而這篇文章主要來講一講IPC的主要方式之一——AIDL方式。除了AIDL方式,IPC還有其他進程間通訊方式,比如Messager、ContentProvider、Socket等,這些以后會講到。現在先說說AIDL的基本使用方法。
二、什么是AIDL?
AIDL全稱:Android Interface Definition Language,即Android接口定義語言。由于不同的進程不能共享內存,所以為了解決進程間通訊的問題,Android使用一種接口定義語言來公開服務的接口,本質上,AIDL非常像一個接口,通過公開接口,讓別的進程調用該接口,從而實現進程間的通訊。
三、使用AIDL
以下結合一個具體實例來示范AIDL的使用方法。
1、建立.aidl文件
為了方便AIDL的開發,建議把所有和AIDL相關的類和文件放入同一個包中,這樣方便把整個包復制,以便其他模塊或者應用需要用到同一個AIDL。在Android Studio下,專門為AIDL文件創建了一個文件夾,方便我們的管理:
項目結構
可以看到,筆者新建了一個service模塊,該模塊在manifests的聲明如下:
<service android:name=".MyAidlService" android:enabled="true" android:exported="true"></service>
這個模塊為app模塊提供服務,與app模塊處于不同進程,所以模擬了進程間通訊的場景。在service模塊,新建一個AIDL文件夾,然后新建一個包,這里包名為com.chenyu.service,然后新建AIDL文件:IMyAidl.aidl:
// IMyAidl.aidl
package com.chenyu.service;// Declare any non-default types here with import statements
import com.chenyu.service.Person;
interface IMyAidl {
void addPerson(in Person person);
List<Person> getPersonList();
}</pre>這與定義一個接口的語法基本相同,都是以Interface為關鍵字定義。里面聲明了兩個方法,分別是addPerson(in Person person)與getPersonList()。AIDL中除了基本數據類型,其他類型的參數必須標上方向,in、out、或者inout,in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數,我們要根據需要實際指定參數類型,因為底層的數據處理開銷非常大,如果不指定類型,編譯將會無法通過。
AIDL支持的數據類型:
①基本數據類型(int,long,char,boolean,double)
②string和CharSequence
③List:只支持ArrayList,以及里面所有的元素必須能夠被AIDL支持 ④Map:只支持HashMap,以及里面所有的元素必須能夠被AIDL支持 ⑤Parcelable:所有實現了Parcelable接口的對象
⑥AIDL:所有AIDL接口本身也可以在AIDL文件中使用。注意一下:這里使用了自定義的Parcelable對象:Person類,但是AIDL不認識這個類,所以我們要創建一個與Person類同名的AIDL文件:Person.aidl
// IMyAidl.aidl package com.chenyu.service; //聲明Person parcelable Person;只有這樣,IMyAidl.aidl才能知道其中的Person是使用了Parcelable接口的類,注意,Person類的包名與Person.aidl的包名一定要相同,即無論其他應用或者其他模塊,只要有AIDL,都應該保證AIDL的所有包結構一致,才能保證順利進行IPC通訊,減少不必要的麻煩。
2、Person類,實現Parcelable接口
在java文件夾中,創建com.chenyu.service包,這樣就與上面的是相同包名了。package com.chenyu.service;import android.os.Parcel;
import android.os.Parcelable;public class Person implements Parcelable {
private String name;
private int age;
private int number;public Person(Parcel source) { this.name=source.readString(); this.age=source.readInt(); this.number=source.readInt(); } //getter、setter method //... public Person(int age, String name, int number) { this.age = age; this.name = name; this.number = number; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); dest.writeInt(number); } public static final Parcelable.Creator<Person> CREATOR=new Creator<Person>() { @Override public Person createFromParcel(Parcel source) { return new Person(source); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public String toString() { return "Person{" + "name='" + name + '\\\\'' + ", age=" + age + ", number=" + number + '}'; }
}</pre>
對于Parcelable接口的詳細解析,可參考上一篇文章,這里不再贅述。
3、實現服務端
上面我們定義了一個AIDL接口,接下來要做的是實現這個AIDL接口,在java/com.chenyu.service中,創建MyAidlService.java文件:package com.chenyu.service;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;import java.util.ArrayList;
import java.util.List;public class MyAidlService extends Service {
private ArrayList<Person> persons;
@Override
public IBinder onBind(Intent intent) {
persons=new ArrayList<Person>();
Log.d("cy", "success bind");
return iBinder;
}
private IBinder iBinder= new IMyAidl.Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
persons.add(person);
}@Override public List<Person> getPersonList() throws RemoteException { return persons; } }; @Override public void onCreate() { super.onCreate(); Log.d("cy", "onCreate "); }
}</pre>
我們來看一下服務端是如何實現接口的:
(1)為了實現來自.aidl文件生成的接口,需要繼承Binder接口(例如Ibinder接口),并且實現從.aidl文件中繼承的方法,在上面代碼中,使用匿名實例實現一個叫IMyAidl(定義在IMyAidl.aidl中)的接口,實現了兩個方法,addPerson和getPersonList.
(2)onBind方法:該方法在客戶端與服務端連接的時候回調,實現客戶端和服務端的綁定,并返回一個Binder實例,這里返回的是iBinder,而IBinder是(1)中實現了接口的匿名實例,即客戶端拿到的實際上實現了接口的一個實例,這樣,客戶端通過Binder就與服務端建立了連接,客戶端通過Binder遠程調用服務端的實例方法,這樣也即實現了進程間通訊。4、實現客戶端
在實現客戶端之前,先確保把aidl的包復制過來,就相上面筆者所給出的結構圖一樣,包括Person類也應該復制過來。顯示界面比較簡單,就不貼出來了,主要看Activity的代碼:
package com.chenyu.myaidl;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.View;
import android.widget.Button;
import com.chenyu.service.IMyAidl;
import com.chenyu.service.Person;import java.util.List;
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn; IMyAidl iMyAidl; private ServiceConnection conn=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("cylog", "onServiceConnected success"); iMyAidl=IMyAidl.Stub.asInterface(service); // 1 } @Override public void onServiceDisconnected(ComponentName name) { Log.d("cylog", "onServicedisConnected "); iMyAidl=null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); bindService(); } private void initView() { btn= (Button) findViewById(R.id.cal); btn.setOnClickListener(this); } @Override public void onClick(View v) { try { iMyAidl.addPerson(new Person(21, "陳育", 22255)); List<Person> persons = iMyAidl.getPersonList(); // 2 Log.d("cylog",persons.toString()); } catch (RemoteException e) { e.printStackTrace(); } } private void bindService() { Intent intent=new Intent(); intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService")); bindService(intent,conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); }
}</pre>
與綁定一般Service的語法差不多,但是在安卓5.0之后,必須顯式指定Service的包名和類名即:
private void bindService() { Intent intent=new Intent(); intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService")); bindService(intent,conn, Context.BIND_AUTO_CREATE); }在bindService(intent,conn,Context.BIND_AUTO_CREATE)方法中有幾個參數需要說明一下,
①conn:該參數代表了與服務端的連接,即ServiceConnection.
②Context.BIND_AUTO_CREATE:該參數表示綁定的同時創建一個Service。
在發出請求綁定成功之后,會回調①處的代碼,此時,可在回調方法onServiceConnected()方法中,獲取服務端返回的IMyAidl實例,在客戶端拿到該實例之后,就可以通過調用相應的方法進行遠程通訊了,比如上述的②處代碼。
最后,看一下運行結果,先運行service,然后運行app:
運行結果
進程顯示
可以看出,app端和service端的確是構成了進程間通訊,并且完成了進程間通訊。
以上是利用AIDL實現進程通訊的基本方法,希望對大家有所幫助。關于AIDL的核心原理以及Binder,AIDL優化,會在下一篇文章詳細講述。