本例要實現的效果是兩個app,client和server,客戶端能調用server端的Service提供的方法。
本次示例分爲客服端和服務器端,兩端的代碼目錄如下:
服務器端代碼目錄:
客戶端代碼目錄:
代碼說明
--->服務器端代碼:
首先,要創建一個AIDL接口,IPersonManager.aidl,裏面定義了服務器端提示給客戶端的方法。
// IPersonManager.aidl
package com.demo.aidlprojectserver;
import com.demo.aidlprojectserver.Person; //要傳遞的自定義對象需要顯示的import,即使是在一個目錄
interface IPersonManager {
List<Person> getPersons();
void addPerson(in Person person); //除了基本數據類型,要添加 in out inout
String greet(String name);
}
系統自動編譯會根據這個IPersonManager.aidl生成一個IPersonManager.java類。如圖所示:
這個IPersonManager.java裏的內部內IPersonManager.Stub就是一個Binder
public static abstract class Stub extends android.os.Binder
提示服務的Service裏new一個這樣的Binder,通過這個Binder向外提示服務。
AIDLService.java
package com.demo.aidlprojectserver;
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 AIDLService extends Service {
public static final String TAG = AIDLService.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
try {
mManage.addPerson(new Person("張三", "男", 22));
mManage.addPerson(new Person("李四", "男", 30));
mManage.addPerson(new Person("婉兒", "女", 18));
} catch (RemoteException e) {
e.printStackTrace();
}
}
private List<Person> mPersons = new ArrayList<>();
private IPersonManager.Stub mManage = new IPersonManager.Stub() {
@Override
public List<Person> getPersons() throws RemoteException {
return mPersons;
}
@Override
public void addPerson(Person person) throws RemoteException {
Log.d(TAG, "client add person-----" + person.toString());
mPersons.add(person);
}
@Override
public String greet(String name) {
return "熱烈祝賀" + name + "來我院視察";
}
};
@Override
public IBinder onBind(Intent intent) {
return mManage;
}
}
由於客戶端也要定義同樣的IPersonManager.aidl(所屬的包目錄結構一樣,裏面的代碼也一樣),生成的IPersonManager.java類也一樣,所以客戶端就可以通過客戶端的IPersonManger來調用服務器端的Binder提示的方法,續而調用到Service提示的操作。
服務器端還有兩個文件沒有介紹:Person.java和Person.aidl。
默認情況下,AIDL 支持下列數據類型:
Java 編程語言中的所有原語類型(如 int、long、char、boolean 等等)
String/CharSequence
List
List 中的所有元素都必須是以上列表中支持的數據類型、其他 AIDL 生成的接口或您聲明的可打包類型。 可選擇將 List 用作“通用”類(例如,List)。另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 接口。
Map
Map 中的所有元素都必須是以上列表中支持的數據類型、其他 AIDL 生成的接口或您聲明的可打包類型。 不支持通用 Map(如 Map< String,Integer> 形式的 Map)。 另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口。
您必須爲以上未列出的每個附加類型加入一個 import 語句,即使這些類型是在與您的接口相同的軟件包中定義。
通過 IPC 傳遞對象
有時一些簡單的數據無法滿足我們的需求,我們需要的一個自定義實體,這時就需要使用Parcelable,要傳遞的實體類必須支持 Parcelable 接口。
//Person.java
package com.demo.aidlprojectserver;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private String name;
private String sex;
private int age;
protected Person(Parcel in) {
this.name = in.readString(); //讀取的順序要和writeToParcel的write順序一致
this.sex = in.readString();
this.age = in.readInt();
}
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(sex);
dest.writeInt(age);
}
@Override
public String toString() {
return name + ":" + sex + ":" + age;
}
}
除了實現Parcelable接口外,系統還要求我們自定義的對象需要寫一個aidl文件。如Person.aidl
// Person.aidl
package com.demo.aidlprojectserver;
parcelable Person;
到此,服務器端代碼介紹完畢
--->客戶端代碼
和服務器端一樣,我們需要編寫IPersonManager.aidl(生成IPersonManager.java)文件,編寫Person.java,Person.aidl文件,並且package要和服務器端一樣。代碼目錄回看上面介紹。
最後直接看調用的代碼MainActivity.java
package com.demo.aidlprojectclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.demo.aidlprojectserver.IPersonManager;
import com.demo.aidlprojectserver.Person;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private boolean connected = false;
private IPersonManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_bind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClassName("com.demo.aidlprojectserver", "com.demo.aidlprojectserver.AIDLService");
connected = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
});
findViewById(R.id.bt_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (connected) {
Person person = new Person("lili", "女", 18);
try {
manager.addPerson(person);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
findViewById(R.id.bt_greet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (connected && manager != null) {
try {
Toast.makeText(MainActivity.this, manager.greet("曹操"), 0).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
manager = IPersonManager.Stub.asInterface(service);
try {
List<com.demo.aidlprojectserver.Person> personList = manager.getPersons();
for (Person person : personList) {
Log.d(TAG, person.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
上面代碼中,就是AIDL通過Binder機制,讓客戶端拿到了服務器端的Binder對象,從而用服務器端的Binder去調用服務器端的Service提示的方法。
本例子的效果就是:客戶端通過調用 manager.greet("曹操"),調用到了另外一個進程(App)的方法:
public String greet(String name) {
return "熱烈祝賀" + name + "來我院視察";
}
};
返回結果是:熱烈祝賀曹操來我院視察
=========================================================================
至此完成了一次通過Binder(AIDL)實現跨進程通信。這和App通過跨進程通信調用系統服務是一樣的道理。如下實例:
Binder運行的實例解釋
首先我們看看我們的程序跨進程調用系統服務的簡單示例,實現浮動窗口部分代碼:
//獲取WindowManager服務引用
WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE);
//佈局參數layoutParams相關設置略...
View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
註冊服務(addService): 在Android開機啓動過程中,Android會初始化系統的各種Service,並將這些Service向ServiceManager註冊(即讓ServiceManager管理)。這一步是系統自動完成的。
獲取服務(getService): 客戶端想要得到具體的Service直接向ServiceManager要即可。客戶端首先向ServiceManager查詢得到具體的Service引用,通常是Service引用的代理對象,對數據進行一些處理操作。即第2行代碼中,得到的wm是WindowManager對象的引用。
+
使用服務: 通過這個引用向具體的服務端發送請求,服務端執行完成後就返回。即第6行調用WindowManager的addView函數,將觸發遠程調用,調用的是運行在systemServer進程中的WindowManager的addView函數。
代碼下載地址:https://github.com/tomyZhou/AidlDemo
更多Binder相關知識推薦如下:
Android 中的 IPC 方式 https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/ipc.html
Android Binder機制及AIDL https://lrh1993.gitbooks.io/android_interview_guide/content/android/advance/binder.html
從getSystemService()開始,開擼Binder通訊機制 https://www.jianshu.com/p/1050ce12bc1e