AIDL

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使用解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末名船,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子旨怠,更是在濱河造成了極大的恐慌渠驼,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鉴腻,死亡現(xiàn)場(chǎng)離奇詭異迷扇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爽哎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蜓席,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人课锌,你說我怎么就攤上這事厨内。” “怎么了渺贤?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵雏胃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我志鞍,道長(zhǎng)瞭亮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任述雾,我火速辦了婚禮街州,結(jié)果婚禮上兼丰,老公的妹妹穿的比我還像新娘玻孟。我一直安慰自己,他們只是感情好鳍征,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布黍翎。 她就那樣靜靜地躺著,像睡著了一般艳丛。 火紅的嫁衣襯著肌膚如雪匣掸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天氮双,我揣著相機(jī)與錄音碰酝,去河邊找鬼。 笑死戴差,一個(gè)胖子當(dāng)著我的面吹牛送爸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼袭厂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼墨吓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纹磺,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤帖烘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后橄杨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秘症,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年式矫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了历极。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衷佃,死狀恐怖趟卸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氏义,我是刑警寧澤锄列,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惯悠,受9級(jí)特大地震影響邻邮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜克婶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一筒严、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧情萤,春花似錦鸭蛙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至睁宰,卻和暖如春肪获,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柒傻。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工孝赫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人红符。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓青柄,卻偏偏與公主長(zhǎng)得像劫映,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刹前,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容