早些時(shí)候就聽(tīng)說(shuō)過(guò)AIDL痕支,也常在各種Android面試題啃勉、教程甚至大牛采訪中看到過(guò)它的身影埠巨∫逶可見(jiàn)AIDL在Android開(kāi)發(fā)中的地位十分的重要虾标。
于是決定先從AIDL的一些基本概念和基本用法開(kāi)始著手學(xué)習(xí)它,下面是一些整理的筆記灌砖。
AIDL的全稱為Android Interface Definition Language, 顧名思義璧函,它主要就是用來(lái)定義接口的一種語(yǔ)言:
AIDL (Android Interface Definition Language) is similar to other IDLs you might have worked with. It allows you to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC). On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL.
Android Developer的官方文檔中對(duì)AIDL做了很好的概括。當(dāng)作為客戶的一方和要和作為服務(wù)器的一方進(jìn)行通信時(shí)基显,需要指定一些雙方都認(rèn)可的接口蘸吓,
這樣才能順利地進(jìn)行通信。而AIDL就是定義這些接口的一種工具撩幽。為什么要借助AIDL來(lái)定義库继,而不直接編寫(xiě)接口呢(比如直接通過(guò)Java定義一個(gè)Interface)?
這里涉及到進(jìn)程間通信(IPC)的問(wèn)題窜醉。和大多數(shù)系統(tǒng)一樣宪萄,在Android平臺(tái)下,各個(gè)進(jìn)程都占有一塊自己獨(dú)有的內(nèi)存空間,各個(gè)進(jìn)程在通常情況下只能訪問(wèn)自己的獨(dú)有的內(nèi)存空間榨惰,而不能對(duì)別的進(jìn)程的內(nèi)存空間進(jìn)行訪問(wèn)拜英。
進(jìn)程之間如果要進(jìn)行通信,就必須先把需要傳遞的對(duì)象分解成操作系統(tǒng)能夠理解的基本類型琅催,并根據(jù)你的需要封裝跨邊界的對(duì)象居凶。而要完成這些封裝工作,需要寫(xiě)的代碼量十分地冗長(zhǎng)而枯燥恢暖。因此Android提供了AIDL來(lái)幫助你完成這些工作排监。
從AIDL的功能來(lái)看,它主要的應(yīng)用場(chǎng)景就是IPC杰捂。雖然同一個(gè)進(jìn)程中的client-service也能夠通過(guò)AIDL定義接口來(lái)進(jìn)行通信舆床,但這并沒(méi)有發(fā)揮AIDL的主要功能。
概括來(lái)說(shuō):
- 如果不需要IPC嫁佳,那就直接實(shí)現(xiàn)通過(guò)繼承Binder類來(lái)實(shí)現(xiàn)客戶端和服務(wù)端之間的通信挨队。
- 如果確實(shí)需要IPC,但是無(wú)需處理多線程蒿往,那么就應(yīng)該通過(guò)Messenger來(lái)實(shí)現(xiàn)盛垦。Messenger保證了消息是串行處理的,其內(nèi)部其實(shí)也是通過(guò)AIDL來(lái)實(shí)現(xiàn)瓤漏。
- 在有IPC需求腾夯,同時(shí)服務(wù)端需要并發(fā)處理多個(gè)請(qǐng)求的時(shí)候颊埃,使用AIDL才是必要的
在了解了基本的概念和使用場(chǎng)景之后,使用AIDL的基本步驟如下:
- 編寫(xiě).AIDL文件蝶俱,定義需要的接口
- 實(shí)現(xiàn)定義的接口
- 將接口暴露給客戶端調(diào)用
下面通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的遠(yuǎn)程Bound Service來(lái)練習(xí)這幾個(gè)步驟:
1. 編寫(xiě).AIDL文件班利,定義需要的接口
在Android Studio下,右鍵src文件夾榨呆,選擇新建AIDL文件罗标,并填寫(xiě)名字,這里我命名為IRemoteService
點(diǎn)擊Finish按鈕之后积蜻,會(huì)發(fā)現(xiàn)main下多了一個(gè)名字為AIDL的目錄闯割,目錄下的包名和Java的包名保持一致,包下即是新建的IRemoteService.aidl
文件竿拆。
內(nèi)容我們編寫(xiě)如下:
// IRemoteService.aidl
package learn.android.kangel.learning;
// Declare any non-default types here with import statements
import learn.android.kangel.learning.HelloMsg;
interface IRemoteService {
HelloMsg sayHello();
}
AIDL的寫(xiě)法和Java十分類似宙拉,這里我定義了一個(gè)sayHello()
方法,用來(lái)獲取一個(gè)從服務(wù)端返回的消息HelloMsg
如输。
這里的HelloMsg
是我自己定義的一個(gè)類型鼓黔。默認(rèn)情況下央勒,AIDL支持下列所述的數(shù)據(jù)類型:
- 所有的基本類型(int不见、float等)
- String
- CharSequence
- List
- Map
其中,List和Map中的元素類型必須是上述類型之一或者由其他AIDL生成的接口類型崔步,或者是已經(jīng)聲明的Pacelable
類型稳吮。
List類型可以指定泛型類,比如寫(xiě)成List<String>
, 并且對(duì)方接收到的具體實(shí)例都是ArrayList
Map類型不支持指定泛型類井濒,比如Map<String,String>
灶似。只能Map表示類型,并且對(duì)方接收到的具體實(shí)例都是HashMap
在這個(gè)IRemoteService
例子中瑞你,我們希望在進(jìn)程間傳遞一個(gè)HelloMsg
對(duì)象:他的定義如下:
/*HelloMsg.java*/
public class HelloMsg {
private String msg;
private int pid;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public HelloMsg(String msg, int pid) {
this.msg = msg;
this.pid = pid;
}
}
為了讓HelloMsg
能夠在進(jìn)程間傳遞酪惭, 它必須實(shí)現(xiàn)Parcelable
接口,Parcelable
是Android提供的一種序列化方式者甲,如果嫌手寫(xiě)麻煩的話春感,通過(guò)插件我們可以十分快捷為現(xiàn)有的類添加Parcelable
實(shí)現(xiàn):
/*HelloMsg.java*/
import android.os.Parcel;
import android.os.Parcelable;
public class HelloMsg implements Parcelable {
private String msg;
private int pid;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public HelloMsg(String msg, int pid) {
this.msg = msg;
this.pid = pid;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.msg);
dest.writeInt(this.pid);
}
protected HelloMsg(Parcel in) {
this.msg = in.readString();
this.pid = in.readInt();
}
public static final Parcelable.Creator<HelloMsg> CREATOR = new Parcelable.Creator<HelloMsg>() {
@Override
public HelloMsg createFromParcel(Parcel source) {
return new HelloMsg(source);
}
@Override
public HelloMsg[] newArray(int size) {
return new HelloMsg[size];
}
};
}
定義好HelloMsg.java
之后,還需要新增一個(gè)與其同名的AIDL文件虏缸。那么同樣按照剛才的步驟右鍵src文件夾鲫懒,添加一個(gè)名為HelloMsg的AIDL文件。
這個(gè)AIDL的編寫(xiě)十分簡(jiǎn)單刽辙,只需要簡(jiǎn)單的聲明一下要用到的Pacelable類即可窥岩,有點(diǎn)類似C語(yǔ)言的頭文件,這個(gè)AIDL文件是不參與編譯的:
// HelloMsg.aidl
package learn.android.kangel.learning;
parcelable HelloMsg;
注意到parcelable
的首字母是小寫(xiě)的宰缤,這算是AIDL一個(gè)特殊的地方颂翼。
接下來(lái)還需要再IRemoteService.aidl
文件中使用import
關(guān)鍵字導(dǎo)入這個(gè)HelloMsg
類型晃洒。詳細(xì)的寫(xiě)法參考上面的IRemoteService.aidl
代碼。
即便IRemoteService.aidl
和HelloMsg.aidl
位于同一個(gè)包下朦乏,這里的import
是必須要有的锥累。這也是AIDL一個(gè)特殊的地方。
好了集歇,至此編寫(xiě).AIDL文件的步驟就基本結(jié)束了桶略,這個(gè)時(shí)候需要make project或者make對(duì)應(yīng)的module,Android SDK就會(huì)根據(jù)我這里編寫(xiě)的.AIDL文件生成對(duì)應(yīng)的Java文件诲宇。
在Android Studio下际歼,可以在build/generated/aidl目錄下找到這些Java文件。
查看IRemoteService.java
姑蓝,可以看到其內(nèi)部有一個(gè)靜態(tài)抽象類Stub
,這個(gè)Stub
繼承自Binder
類鹅心,并抽象實(shí)現(xiàn)了其父接口,這里對(duì)應(yīng)的是IRemoteService
這個(gè)接口:
public static abstract class Stub extends android.os.Binder implements learn.android.kangel.learning.IRemoteService
Stub
類除了聲明了IRemoteService.aidl
中的所有方法纺荧,還提供了一些有用的helper方法旭愧,比如asInterface()
:
public static learn.android.kangel.learning.IRemoteService asInterface(android.os.IBinder obj)
這個(gè)方法接受一個(gè)Binder
對(duì)象,并將其轉(zhuǎn)化成Stub
對(duì)應(yīng)的接口對(duì)象(也就是這里的IRemoteService
)并返回宙暇。
對(duì)于這些生成的Java文件的進(jìn)一步研究和學(xué)習(xí)可以幫助我們更好地理解Android的Binder
输枯,我會(huì)在之后發(fā)布的學(xué)習(xí)筆記中做相應(yīng)的記錄(挖坑233)
2. 實(shí)現(xiàn)定義的接口
要實(shí)現(xiàn)定義的接口,只需要繼承自生成的Binder類占贫,并實(shí)現(xiàn)其中的方法即可:
IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public HelloMsg sayHello() throws RemoteException {
return new HelloMsg("msg from service at Thread " + Thread.currentThread().toString() + "\n" +
"tid is " + Thread.currentThread().getId() + "\n" +
"main thread id is " + getMainLooper().getThread().getId(), Process.myPid());
}
};
這里的實(shí)現(xiàn)十分簡(jiǎn)單桃熄,返回一個(gè)HelloMsg
,消息部分是當(dāng)前線程的信息,當(dāng)前線程的id型奥,以及主線程的id瞳收,Process Id部分就是當(dāng)前進(jìn)程的Id
3. 將接口暴露給客戶端調(diào)用
在此之前需要了解Bound Service,關(guān)于Bound Service的具體細(xì)節(jié)可以參考這里厢汹,本次筆記不再做額外記錄螟深。
需要注意一點(diǎn),如果希望多個(gè)Application都能夠通過(guò)這個(gè)接口與服務(wù)端通信烫葬,那么所有使用這個(gè)接口的Application的src目錄下都要有對(duì)應(yīng).aidl文件的副本界弧。
在這個(gè)例子中我們編寫(xiě)一個(gè)名為RemoteService
的Service
類,并在onBind()
方法中返回上述第二步中實(shí)現(xiàn)的接口,這樣就把接口傳給了客戶端供其調(diào)用:
package learn.android.kangel.learning;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.widget.Toast;
/**
* Created by Kangel on 2016/7/21.
*/
public class RemoteService extends Service {
IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public HelloMsg sayHello() throws RemoteException {
return new HelloMsg("msg from service at Thread " + Thread.currentThread().toString() + "\n" +
"tid is " + Thread.currentThread().getId() + "\n" +
"main thread id is " + getMainLooper().getThread().getId(), Process.myPid());
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
以上三步完成之后厘灼,我們來(lái)繼續(xù)完善這個(gè)例子來(lái)進(jìn)行一些測(cè)試:
編寫(xiě)作為客戶端的Activity:
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
/**
* Created by Kangel on 2016/7/21.
*/
public class ClientActivity extends AppCompatActivity {
private IRemoteService mRemoteService = null;
private boolean mBind = false;
private TextView mPidText;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acticity_client);
mPidText = (TextView) findViewById(R.id.my_pid_text_view);
mPidText.setText("the client pid is " + Process.myPid());
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, RemoteService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(mConnection);
mBind = false;
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemoteService = IRemoteService.Stub.asInterface(service);
mBind = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteService = null;
mBind = false;
}
};
public void onButtonClick(View view) {
switch (view.getId()) {
case R.id.show_pid_button:
if (mBind) {
try {
Log.i("HELLO_MSG", "the service pid is " + mRemoteService.sayHello().getPid());
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case R.id.say_hello_button:
if (mBind) {
try {
Log.i("HELLO_MSG", mRemoteService.sayHello().getMsg());
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
}
}
}
布局文件中有兩個(gè)Button和一個(gè)TextView夹纫,Button的點(diǎn)擊事件都在xml文件中完成了注冊(cè)。分別用來(lái)獲取服務(wù)端返回的Pid和返回的Msg设凹。
TextView用于展示當(dāng)前Activity所在線程的id舰讹。
在onServiceConnected()
回調(diào)中,我們使用IRemoteService.Stub.asInterface(Binder)
方法返回我們的接口的引用闪朱。接著客戶端就可以通過(guò)它來(lái)對(duì)服務(wù)端發(fā)送請(qǐng)求了月匣。
onButtonClick()
方法中就是對(duì)接口的調(diào)用钻洒。
如果客戶端和服務(wù)端處于同一個(gè)進(jìn)程,onServiceConnected()
回調(diào)中锄开,是可以通過(guò)強(qiáng)制類型轉(zhuǎn)換將返回的Binder
對(duì)象轉(zhuǎn)換為我們需要的接口對(duì)象的素标,像這樣:
mRemoteService = (IRemoteService) service;
但如果客戶端和服務(wù)端處于不同進(jìn)程,執(zhí)行這樣的強(qiáng)轉(zhuǎn)萍悴,系統(tǒng)會(huì)報(bào)錯(cuò):
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to learn.android.kangel.learning.IRemoteService
我的對(duì)此理解是头遭,由于不同進(jìn)程之間的內(nèi)存空間是不能夠互相訪問(wèn)的,A進(jìn)程中的對(duì)象當(dāng)然也就不能為B進(jìn)程所理解癣诱。因此強(qiáng)制類型轉(zhuǎn)換只適用于同一個(gè)進(jìn)程中计维。
在Manifest中聲明作為服務(wù)端的Service和作為客戶端的Acticity
<activity android:name=".ClientActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".RemoteService"
android:process=":remote" />
在這里我為RemoteService設(shè)置了process屬性,讓它運(yùn)行在與默認(rèn)進(jìn)程不同的進(jìn)程中撕予。
接下來(lái)運(yùn)行我們的應(yīng)用:
可以看到客戶端進(jìn)程id為31704
嘗試點(diǎn)擊兩個(gè)按鈕鲫惶,查看Log:
可以看到服務(wù)端的進(jìn)程id為31720,不同于客戶端進(jìn)程实抡。
而且可以看到欠母,service所在的主線程id為1,而處理該請(qǐng)求的線程id為4621吆寨。
來(lái)自遠(yuǎn)程進(jìn)程的調(diào)用分發(fā)自系統(tǒng)為你的進(jìn)程所維持的一個(gè)線程池中赏淌。這也許有點(diǎn)難理解。假如你通過(guò)AIDL實(shí)現(xiàn)了一個(gè)遠(yuǎn)程服務(wù)端的接口鸟废,然后有另外一個(gè)客戶端進(jìn)程調(diào)用了該接口中的方法猜敢,因?yàn)榭蛻舳撕湍闼鶎?shí)現(xiàn)的服務(wù)端處于兩個(gè)不同的進(jìn)程,
因此客戶端對(duì)于你而言盒延,就是一個(gè)遠(yuǎn)程進(jìn)程。當(dāng)客戶端對(duì)接口進(jìn)行調(diào)用時(shí)鼠冕,調(diào)用過(guò)程并不是由客戶端進(jìn)程進(jìn)行處理的添寺。而是由系統(tǒng)進(jìn)行封裝后,傳遞到服務(wù)端進(jìn)程所持有的一個(gè)線程池中進(jìn)行處理懈费。最終線程池中的其中一個(gè)線程會(huì)被用來(lái)執(zhí)行調(diào)用的具體邏輯计露。
而具體選擇哪個(gè)線程來(lái)進(jìn)行處理,是無(wú)法提前預(yù)知的憎乙。
因此作為服務(wù)端接口的實(shí)現(xiàn)者票罐,應(yīng)該能夠處理多線程并發(fā)的情況,時(shí)刻準(zhǔn)備好處理來(lái)自未知線程的調(diào)用泞边,并能保證AIDL接口的實(shí)現(xiàn)是線程安全的该押。
如果服務(wù)端和客戶端處于同一個(gè)進(jìn)程,那么服務(wù)端將會(huì)在與發(fā)起請(qǐng)求的客戶端所處的相同線程上處理該請(qǐng)求阵谚。把上述android:process=":remote"
屬性去掉蚕礼,則可以對(duì)其進(jìn)行驗(yàn)證烟具。
但這種單進(jìn)程的情況,AIDL的使用實(shí)際上是完全沒(méi)必要的奠蹬。
參考鏈接: