AndroidIPC-AIDL
一、概述
AIDL意思即Android Interface Definition Language,翻譯過來就是android接口定義語言,是用於定義服務器和客戶端通信接口的一種描述語言,可以拿來生成用於IPC的代碼。從某種意義上說AIDL其實是一個模板,因爲在使用過程中,實際起作用的並不是AIDL文件,而是據此生成的一個Interface的實例代碼,AIDL其實是爲了避免我們重複編寫代碼而出現的一個模板。
通過AIDL,可以在一個進程中獲取另一個進程的數據和調用其暴露出來的方法,從而滿足進程間通信的需求。通常,暴露方法給其他應用進行調用的應用稱爲服務端,調用其他應用的方法的應用稱爲客戶端,客戶端通過綁定服務端的Service來進行交互。
二、語法
AIDL的語法十分簡單,與Java語言基本保持一致,需要記住的規則有以下幾點:
1.AIDL文件以.aidl爲後綴名
2.AIDL支持的數據類型分爲以下幾種
- 八種基本數據類型
- String,CharSequence
- 實現了Parcelable接口的數據類型
- List類型,List承載的數據必須是AIDL支持的類型,或者是其他生命的AIDL對象
- Map類型,Map承載的數據必須是AIDL支持的類型,或者是其他生命的AIDL對象
3.AIDL文件可以分爲兩類。一類用來聲明實現了Parcelable接口的數據類型,以供其他AIDL文件使用那些非默認支持的數據類型。還有一類用來定義接口方法,聲明要暴露哪寫接口給客戶端調用,定向Tag用來標註這些方法的參數
4.定向Tag。定向Tag表示在跨進程通信中數據的流向,用於標註方法的參數值,分別爲in,out,inout三種。其中in表示數據只能從客戶端流向服務端,out表示數據只能由服務端流向客戶端,而inout則表示數據可以在服務端口和客戶端之間雙向流通。此外,如果AIDL方法接口的參數值類型是基本數據類型,那麼這些參數的定向Tag默認且只能是in
5.明確導包。在AIDL文件中需要明確標明引用到的數據類型所在的包名,即使兩個文件處在同一個包下。
三、服務端編碼
新建一個工程,包名爲com.example.aidlserver
首先,在應用中需要用到一個Student類,而Student類是兩個應用間都需要用到的,所以也需要在AIDL中聲明Student類,爲了避免出現類名重複導致無法創建文件的錯誤,這裏需要先建立StudentAidl文件,之後再創建Student類
右鍵點擊新建一個AIDL文件,命名爲Student。創建完成之後,系統會默認創建一個aidl文件夾
可以刪掉文件中自動生成的接口和方法,因爲這個aidl文件主要是爲了聲明Student類
接下來定義Student類,並且實現Parcelable接口
public class Student implements Parcelable {
private String name;
private int age;
private boolean isAdult;
public Student(){}
public Student(String name, int age, boolean isAdult) {
this.name = name;
this.age = age;
this.isAdult = isAdult;
}
protected Student(Parcel in) {
name = in.readString();
age = in.readInt();
isAdult = in.readByte() != 0;
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isAdult() {
return isAdult;
}
public void setAdult(boolean adult) {
isAdult = adult;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeByte((byte) (isAdult ? 1 : 0));
}
public void readFromParcel(Parcel parcel){
name = parcel.readString();
age = parcel.readInt();
isAdult = parcel.readByte()==1;
}
}
然後,定義接口aidl文件,我們提供四個方法
interface IMyAidlInterface {
String getName();
void sendStudent(inout Student student);
void sendStudentIn(in Student student);
void sendStudentOut(out Student student);
}
第一個方法用於向客戶端返回數據
第二,三,四個方法用於客戶端向服務端提交數據,分別驗證三個定向Tag的區別
之前說過,在進程間通信中真正起作用的並不是AIDL文件,而是系統據此而生成的文件,可以在以下目錄查看系統生成的文件。
創建或者修改AIDL文件後,需要Make Project,這樣系統才能生成我們需要的文件
接下來需要創建一個Service供客戶端遠程綁定了
public class MyService extends Service {
private IMyAidlInterface iMyAidlInterface = new IMyAidlInterface.Stub() {
@Override
public String getName() throws RemoteException {
return "test";
}
@Override
public void sendStudent(Student student) throws RemoteException {
Log.d("InOut", "name = " +student.getName());
Log.d("InOut", "age = "+student.getAge());
Log.d("InOut", "isAdult = " +student.isAdult());
student.setName("服務端修改了student的name InOut");
}
@Override
public void sendStudentIn(Student student) throws RemoteException {
Log.d("In", "name = " +student.getName());
Log.d("In", "age = "+student.getAge());
Log.d("In", "isAdult = " +student.isAdult());
student.setName("服務端修改了student的name In");
}
@Override
public void sendStudentOut(Student student) throws RemoteException {
Log.d("Out", "name = " +student.getName());
Log.d("Out", "age = "+student.getAge());
Log.d("Out", "isAdult = " +student.isAdult());
student.setName("服務端修改了student的name Out");
}
};
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return iMyAidlInterface.asBinder();
}
}
可以看到,我們聲明瞭一個IMyAidlInterface的實例,並重寫了它的四個方法。
在onBind方法中我們返回的是iMyAidlInterface.asBinder();
最後,服務端還有一個地方需要注意
因爲服務端的Service需要被客戶端遠程綁定,所以客戶端要能找到這個Service,可以通過先指定包名,之後再配置action的值來找到Service,需要在manifest文件中修改Service的註冊。
<service>
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="serverService"/>
</intent-filter>
</service>
四、客戶端編碼
客戶端需要再新建一個工程,包名爲com.example.aidlclient
將服務端中的AIDL文件以及Student類複製過來,將aidl文件夾整個複製到和java文件夾同一層級下,不需要改動任何代碼
AIDLServer
AIDLClient
修改佈局文件,添加三個按鈕
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="InOut"
android:id="@+id/inout"
android:onClick="onClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="In"
android:id="@+id/in"
android:onClick="onClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Out"
android:id="@+id/out"
android:onClick="onClick"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private IMyAidlInterface iMyAidlInterface;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("serverService");
intent.setPackage("com.example.aidlserver");
bindService(intent,connection,BIND_AUTO_CREATE);
}
public void onClick(View view) {
switch(view.getId()){
case R.id.inout:
Student student = new Student("inout student",20,true);
try {
iMyAidlInterface.sendStudent(student);
Log.d(TAG, "新名字 = "+student.getName());
Toast.makeText(this, iMyAidlInterface.getName(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.out:
Student studentOut = new Student("out student",20,true);
try {
iMyAidlInterface.sendStudentOut(studentOut);
Log.d(TAG, "新名字 = "+studentOut.getName());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.in:
Student studentIn = new Student("in student",20,true);
try {
iMyAidlInterface.sendStudentIn(studentIn);
Log.d(TAG, "新名字 = "+studentIn.getName());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
先綁定ServiceiMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
,通過這行代碼獲取到接口的實例,通過這個調用那四個方法。
我們按順序分別點擊了InOut,In,Out
結果
InOut
AIDLClient
AIDLServer
可以看到,student的信息可以傳到服務端中,而且服務端中對name的修改也會同步到客戶端
In
AIDLClient
AIDLServer
可以看到,數據可以從客戶端傳到服務端,但是服務端對name的修改並沒有同步到客戶端
也就是說數據只能從客戶端傳到服務端
Out
AIDLClient
AIDLServer
可以看到,客戶端的數據並沒有傳入到服務端中,而服務端中對name的修改卻同步到了客戶端
也就是說數據只能從服務端傳到客戶端。