Android中AIDL的基本用法
Android 中AIDL的使用與理解
Android AIDL使用詳解
徹底明白Android中AIDL及其使用
AIDL使用解析
AIDL 簡(jiǎn)介
AIDL 的全稱為Android Interface Definition Language才睹,是一種android內(nèi)部進(jìn)程通信接口的描述語言帕识,通過它我們可以定義進(jìn)程間的通信接口。
- 當(dāng)作為客戶的一方和要和作為服務(wù)器的一方進(jìn)行通信時(shí)泉哈,需要指定一些雙方都認(rèn)可的接口匠襟, 這樣才能順利地進(jìn)行通信坏晦。而AIDL就是定義這些接口的一種工具滚婉。為什么要借助AIDL來定義凰狞,而不直接編寫接口呢(比如直接通過Java定義一個(gè)Interface)豁陆? 這里涉及到進(jìn)程間通信(IPC)的問題夏哭。和大多數(shù)系統(tǒng)一樣,在Android平臺(tái)下,各個(gè)進(jìn)程都占有一塊自己獨(dú)有的內(nèi)存空間献联,各個(gè)進(jìn)程在通常情況下只能訪問自己的獨(dú)有的內(nèi)存空間竖配,而不能對(duì)別的進(jìn)程的內(nèi)存空間進(jìn)行訪問何址。 進(jìn)程之間如果要進(jìn)行通信,就必須先把需要傳遞的對(duì)象分解成操作系統(tǒng)能夠理解的基本類型进胯,并根據(jù)你的需要封裝跨邊界的對(duì)象用爪。而要完成這些封裝工作,需要寫的代碼量十分地冗長(zhǎng)而枯燥胁镐。因此Android提供了AIDL來幫助你完成這些工作偎血。
IPC:inter-process communication :進(jìn)程間通信
什么時(shí)候使用 AIDL?
- 如果不需要IPC盯漂,那就直接實(shí)現(xiàn)通過繼承Binder類來實(shí)現(xiàn)客戶端和服務(wù)端之間的通信颇玷。
- 如果確實(shí)需要IPC,但是無需處理多線程就缆,那么就應(yīng)該通過Messenger來實(shí)現(xiàn)帖渠。Messenger保證了消息是串行處理的,其內(nèi)部其實(shí)也是通過AIDL來實(shí)現(xiàn)竭宰。
- 在有IPC需求空郊,同時(shí)服務(wù)端需要并發(fā)處理多個(gè)請(qǐng)求的時(shí)候,使用AIDL才是必要的切揭。
一句話總結(jié):
只有當(dāng)你允許來自不同的客戶端訪問你的服務(wù)并且需要處理多線程問題時(shí)你才必須使用AIDL狞甚。
使用 AIDL
一、服務(wù)端
1. 編寫 .aidl 文件廓旬,定義需要的接口
在Android Studio下哼审,右鍵src文件夾,選擇新建AIDL文件孕豹,并填寫名字涩盾,點(diǎn)擊Finish后,會(huì)發(fā)現(xiàn)main下多了一個(gè)名字為AIDL的目錄巩步,目錄下的包名和Java的包名保持一致旁赊,包下即是新建的.aidl文件:
內(nèi)容我們編寫如下:
package com.rickqiu.aidltest;
interface IMyAidlInterface {
String addString(String string);
}
接口里創(chuàng)建了一個(gè)很簡(jiǎn)單的抽象方法 addString(),用來給客戶端調(diào)用椅野,作用很簡(jiǎn)單终畅,接收客戶端發(fā)來的 String 并在后面加上一段 String 后返回給客戶端。
最后make project或者make對(duì)應(yīng)的module竟闪,Android SDK就會(huì)根據(jù)我這里編寫的.AIDL文件生成對(duì)應(yīng)的Java文件离福。
在Android Studio下,可以在build/generated/aidl目錄下找到這些Java文件炼蛤。
2. 實(shí)現(xiàn)定義的接口妖爷,并將接口暴露給客戶端調(diào)用
創(chuàng)建一個(gè)服務(wù),利用 new IMyAidlInterface.Stub() 實(shí)現(xiàn) AIDL 接口里的方法,并得到一個(gè) Binder絮识,并在 Service 的 onBind() 方法里返回這個(gè) Binder绿聘。
public class MyService extends Service {
private String s="service return";
private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub(){
@Override
public String addString(String string) throws RemoteException {
Log.d("ppqq","收到客戶端信息:"+string);
return string+"+"+s;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
}
Stub 是 IMyAidlInterface.java 里的一個(gè)靜態(tài)抽象類,它繼承了 Binder 類次舌,并抽象實(shí)現(xiàn)了 IMyAidlInterface 接口熄攘。
記得注冊(cè) Service,并加上一個(gè) action:
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.rickqiu.aidltest.MyService"/>
</intent-filter>
</service>
到此服務(wù)端的準(zhǔn)備完成了彼念。
二挪圾、客戶端:
1. 創(chuàng)建和服務(wù)端一模一樣的 .aidl 文件
服務(wù)端和客戶端的 AIDL 接口要完全相同,包括包名逐沙,也就是說要在不同工程下建立相同的包名哲思,不然會(huì)出現(xiàn) Java.lang.SecurityException: Binder invocation to an incorrect interface 這個(gè)錯(cuò)誤。
服務(wù)端:
客戶端:
一樣 make project 出 .java吩案。
2. 創(chuàng)建 ServiceConnection棚赔,在 ServiceConnection 的 onServiceConnected() 方法里通過 IMyAidlInterface.Stub.asInterface(IBinder service) 獲得 IMyAidlInterface 接口的對(duì)象,通過這個(gè)對(duì)象就能調(diào)用服務(wù)端的方法了
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection connection;
private EditText editText;
private TextView textView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.edit);
textView = (TextView) findViewById(R.id.text);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setPackage("com.rickqiu.aidltest");
intent.setAction("com.rickqiu.aidltest.MyService");
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("ppqq", "Service Connected");
// 這里的service就是服務(wù)端onBinde()方法里返回的Binder
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
String result="";
try {
result=myAidlInterface.addString(editText.getText().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
textView.setText(result);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("ppqq", "Service Disconnected");
}
};
}
}
3. 綁定服務(wù)端的 Service务热,綁定成功時(shí)會(huì)調(diào)用 onServiceConnected() 方法并實(shí)現(xiàn)和服務(wù)端的通信
Intent intent = new Intent();
intent.setPackage("com.rickqiu.aidltest");
intent.setAction("com.rickqiu.aidltest.MyService");
bindService(intent, connection, BIND_AUTO_CREATE);
注意忆嗜,Android5.0 后不能直接隱式啟動(dòng)服務(wù)己儒,不過可以多設(shè)置一下包名來實(shí)現(xiàn)隱式啟動(dòng)崎岂。intent.setPackage("com.rickqiu.aidltest")
注意這是 Service 所在的 Project 的包名。
三闪湾、傳遞自定義類型
默認(rèn)情況下冲甘,AIDL支持下列所述的數(shù)據(jù)類型:
- 所有的基本類型(int、float等)
- String
- CharSequence
- List
- Map
其中途样,List和Map中的元素類型必須是上述類型之一或者由其他AIDL生成的接口類型江醇,或者是已經(jīng)聲明的Pacelable類型。
List類型可以指定泛型類何暇,比如寫成List<String>, 并且對(duì)方接收到的具體實(shí)例都是ArrayList陶夜。
Map類型不支持指定泛型類,比如Map<String,String>裆站。只能Map表示類型条辟,并且對(duì)方接收到的具體實(shí)例都是HashMap。
如果想在進(jìn)程間傳遞其他類型宏胯,必須實(shí)現(xiàn)Parcelable接口實(shí)現(xiàn)序列化羽嫡。
比如想在進(jìn)程間傳遞一個(gè)Book對(duì)象:
服務(wù)端;
IMyAidlInterface.aidl:
package com.rickqiu.aidltest;
import com.rickqiu.aidltest.Book;
interface IMyAidlInterface {
Book getBook();
void setBook(in Book book);
int getProcessId();
}
先要把 Book 序列化:
Book.java:
package com.rickqiu.aidltest;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable{
private int id;
private String name;
private float price;
public Book() {
}
public Book(int id, String name, float price) {
this.id = id;
this.name = name;
this.price = price;
}
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 float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeFloat(price);
}
public final static Creator<Book> CREATOR = new Creator<Book>(){
@Override
public Book createFromParcel(Parcel source) {
Book book=new Book();
book.id=source.readInt();
book.name=source.readString();
book.price=source.readFloat();
return book;
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
定義好Book.java之后肩袍,還需要新增一個(gè)與其同名的AIDL文件杭棵。那么同樣按照剛才的步驟右鍵src文件夾,添加一個(gè)名為Book的AIDL文件氛赐。
這個(gè)AIDL的編寫十分簡(jiǎn)單魂爪,只需要簡(jiǎn)單的聲明一下要用到的Pacelable類即可先舷,有點(diǎn)類似C語言的頭文件,這個(gè)AIDL文件是不參與編譯的滓侍。
注意到parcelable的首字母是小寫的密浑,這算是AIDL一個(gè)特殊的地方。
Book.aidl:
package com.rickqiu.aidltest;
parcelable Book;
接下來還需要再IMyAidlInterface.aidl文件中使用import關(guān)鍵字導(dǎo)入這個(gè)Book類型粗井。詳細(xì)的寫法參考上面的IMyAidlInterface.aidl代碼尔破。
即便IMyAidlInterface.aidl和Book.aidl位于同一個(gè)包下,這里的import是必須要有的浇衬。這也是AIDL一個(gè)特殊的地方懒构。
文件結(jié)構(gòu);
客戶端:
客戶端也需要一個(gè) Parcelable 過的 Book.java耘擂。
也同樣需要 IMyAidlInterface.aidl 和 Book.aidl胆剧。
注意,Book.aidl 的包名要與客戶端這里的 Book.java 相同醉冤,IMyAidlInterface.aidl 里 import 的是客戶端這邊 Book 的包名秩霍。
IMyAidlInterface.aidl:
package com.rickqiu.aidltest;
import com.rickqiu.aidltestclient.Book;
interface IMyAidlInterface {
String addString(String string);
Book getBook();
void setBook(in Book book);
int getProcessId();
}
文件結(jié)構(gòu);
方向指示符
注意到 IMyAidlInterface.aidl 里 Book 參數(shù)前面有個(gè) in蚁阳。
- 非原始類型中铃绒,除了String和CharSequence以外,其余均需要一個(gè)方向指示符螺捐。
- in表示由客戶端設(shè)置颠悬。
- out表示由服務(wù)端設(shè)置。
- inout表示客戶端和服務(wù)端都設(shè)置了該值定血。
四赔癌、效果
不打開AIDLTest應(yīng)用,打開AIDLTestClient應(yīng)用澜沟,輸入字符串:
按下按鍵綁定服務(wù)灾票,給服務(wù)端提供數(shù)據(jù),并接收服務(wù)端返回的數(shù)據(jù):
五茫虽、回調(diào)
上面的簡(jiǎn)單例子刊苍,是客戶端主動(dòng)傳輸數(shù)據(jù)給服務(wù)器,服務(wù)器只能被動(dòng)通過 return 返回?cái)?shù)據(jù)給客戶端席噩。如果想服務(wù)器主動(dòng)傳輸數(shù)據(jù)給客戶端班缰,可以注冊(cè)一個(gè)回調(diào)。
- IRemoteService.aidl:
interface IRemoteService
{
void registerCallback(in IRemoteCallback cb);
void unregisterCallback(in IRemoteCallback cb);
......
}
- IRemoteCallback.aidl:
interface IRemoteCallback
{
void onConnection(in int status);
void onConfig(in Config config);
void onCommand(in Command cmd);
void onResponse(in Response rsp);
void onPackage(in Package pkg);
}
-
客戶端:
在客戶端里設(shè)置好回調(diào) IRemoteCallback 的各個(gè)方法悼枢,然后通過 registerCallback(in IRemoteCallback cb) 傳入 IRemoteService埠忘,之后在服務(wù)器就能調(diào)用 IRemoteCallback 的各個(gè)方法了:
private IRemoteCallback mCallback = new IRemoteCallback.Stub()
{
@Override
public void onConfig(Config config) throws RemoteException {
......
}
@Override
public void onCommand(Command cmd) throws RemoteException {
......
}
@Override
public void onResponse(Response rsp) throws RemoteException {
......
}
@Override
public void onPackage(Package pkg) throws RemoteException {
......
}
@Override
public void onConnection(int status) throws RemoteException {
......
}
};
......
mRemoteService.registerCallback(mCallback);
-
服務(wù)器端:
因?yàn)橛玫?AIDL 時(shí)一般都是多線程并發(fā)的情況,也就是可能有好幾個(gè)客戶端通過 AIDL 連接服務(wù)器,并都注冊(cè)了一個(gè)回調(diào)莹妒,這就要通過 RemoteCallbackList 來管理多個(gè)不同客戶端的回調(diào):
private RemoteCallbackList<IRemoteCallback> mRemoteCallbacks = new RemoteCallbackList<IRemoteCallback>();
......
private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public void registerCallback(IRemoteCallback cb) throws RemoteException {
if (cb != null) {
mRemoteCallbacks.register(cb);
}
}
@Override
public void unregisterCallback(IRemoteCallback cb) throws RemoteException {
if (cb != null) {
mRemoteCallbacks.unregister(cb);
}
}
調(diào)用回調(diào)的方法:
int cbCount = mRemoteCallbacks.beginBroadcast();
for (int i = 0; i < cbCount; i++) {
final IRemoteCallback cb = mRemoteCallbacks.getBroadcastItem(i);
try {
cb.onConnection(connectionStatus);
} catch (RemoteException e) {
e.printStackTrace();
}
}
mRemoteCallbacks.finishBroadcast();
AIDL 高級(jí)示例
見 AIDL使用解析