詳解Android跨進(jìn)程IPC通信AIDL機(jī)制原理
簡(jiǎn)介
AIDL:Android Interface Definition Language,即Android接口定義語(yǔ)言,用于生成Android不同進(jìn)程間進(jìn)行進(jìn)程通信(IPC)的代碼,一般情況下一個(gè)進(jìn)程是無(wú)法訪問(wèn)另一個(gè)進(jìn)程的內(nèi)存的。如果某些情況下仍然需要跨進(jìn)程訪問(wèn)內(nèi)存數(shù)據(jù),這時(shí)候Android系統(tǒng)就要將其對(duì)象分解成能夠識(shí)別的原數(shù)據(jù),編寫(xiě)這一組操作的代碼是一項(xiàng)繁瑣的工作,但是AIDL對(duì)底層進(jìn)行了抽象的封裝,簡(jiǎn)化了跨進(jìn)程操作。
AIDL IPC機(jī)制是面向接口的,像COM或Corba一樣,但是更加輕量級(jí)。它是使用代理類在客戶端和實(shí)現(xiàn)端傳遞數(shù)據(jù)。
在Android中跨進(jìn)程操作的方式不止一種,四大組件中ContentProvider天生就是為跨進(jìn)程操作而存在的,但是ContentProvider所謂的跨進(jìn)程操作數(shù)據(jù),這些數(shù)據(jù)不一定是存放在內(nèi)存中的,如通訊錄數(shù)據(jù)時(shí)存放在Sqlite數(shù)據(jù)庫(kù)中的。AIDL支持的跨進(jìn)程操作的數(shù)據(jù)是要存放在內(nèi)存中的,AIDL底層實(shí)際上也是使用的Binder進(jìn)行的跨進(jìn)程操作,后續(xù)另起一篇博文繼續(xù)介紹Binder的跨進(jìn)程機(jī)制。
使用場(chǎng)景
只有不同應(yīng)用之間需要進(jìn)行IPC,并且想要在Service中處理多線程時(shí),這種場(chǎng)景才有必要使用AIDL。如果僅僅需要跨進(jìn)程但是不是跨應(yīng)用,這時(shí)候應(yīng)該通過(guò)Binder進(jìn)行數(shù)據(jù)交互;另外如果僅僅是需要跨進(jìn)程IPC,但是不需要處理多線程,這時(shí)候應(yīng)該通過(guò)Messenger類進(jìn)行數(shù)據(jù)交互。
定義AIDL接口
在Android Studio中使用AIDL的項(xiàng)目的目錄結(jié)構(gòu)跟eclipse中有很大差異,下圖是使用AIDL的項(xiàng)目的目錄結(jié)構(gòu)。
在Android Studio中只需要在某個(gè)Module中使用右鍵菜單中new就會(huì)顯示創(chuàng)建AIDL文件的菜單,當(dāng)新建成功后AIDL文件位于工程的同java同一級(jí)的aidl目錄文件夾下面。在 .aidl 文件中存放的就是AIDL接口。
定義.aidl文件
.aidl文件名稱必須同接口名稱保持一致,必須使用Java語(yǔ)言的語(yǔ)法定義AIDL文件。AIDL使用簡(jiǎn)單語(yǔ)法,通過(guò)可帶參數(shù)和返回值的一個(gè)或多個(gè)方法來(lái)聲明接口。參數(shù)和返回值可以是任意類型,甚至可以是其他 AIDL 生成的接口。每個(gè).aidl文件都必須定義單個(gè)接口,并且只需包含接口聲明和方法簽名,也意味著在.aidl文件中接口名稱和方法名稱都不可以使用權(quán)限修飾符。
默認(rèn)情況下,AIDL 支持下列數(shù)據(jù)類型:
- Java語(yǔ)言中所有的基本數(shù)據(jù)類型,字符類型char,布爾類型boolean以及數(shù)值類型byte、short、int、long、float、double;
- String和CharSequence類型;
- 集合List類型,List中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型??蛇x擇將 List用作泛型類型(例如,List )。另一端實(shí)際接收的具體類始終是 ArrayList,但生成的方法使用的是List接口, 在AIDL中不可以使用ArrayList類型進(jìn)行定義,只能使用List接口定義 ,否則會(huì)報(bào)unknown type編譯錯(cuò)誤。
- 集合Map類型,中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型。不同于集合List接口, 在AIDL中不支持泛型Map(如 Map 形式的 Map)。 另一端實(shí)際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口。類似List接口, 在.aidl文件中不能使用HashMap,只能使用Map接口 。
- 自定義類型必須實(shí)現(xiàn)Parcelable接口,并且在aidl文件夾下有對(duì)應(yīng)類型的aidl文件;
- 非JDK中定義的類型,類似于Java語(yǔ)法,必須使用import進(jìn)行引入。
定義AIDL接口時(shí)需要注意如下:
- 方法可帶零個(gè)或多個(gè)參數(shù),返回值或空值,但是方法名稱不能相同;
- 所有非基本數(shù)據(jù)類型參數(shù)都需要指示數(shù)據(jù)走向的方向標(biāo)記??梢允?in、out 或 inout?;緮?shù)據(jù)類型默認(rèn)為 in,不能是其他方向;
- .aidl 文件中包括的所有代碼注釋都包含在生成的 IBinder 接口中(import 和 package 語(yǔ)句之前的注釋除外);
- 只支持方法,不應(yīng)在AIDL中定義靜態(tài)字段。
如下是定義是IRemoteService.aidl:
package com.sunny.server; import com.sunny.server.bean.User; interface IRemoteService { void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); String getName(); void setListData(in List<String> inList,out List<String> outList); void setMapData(in Map map); void setUser(in User user); }
下面是自定義的JavaBean類型User.aidl
package com.sunny.server.bean; parcelable User;
User類在使用的時(shí)候必須實(shí)現(xiàn)Parcelable接口,代碼這里就不再貼出來(lái)了。
Service實(shí)現(xiàn)AIDL接口
在定義的AIDL接口編譯后實(shí)際上會(huì)生成一個(gè)跟.aidl同名的Java類文件,里面包含了所有的AIDL文件中聲明的方法,并且包含了一個(gè)默認(rèn)的實(shí)現(xiàn)類Stub,該類是抽象類,繼承了Binder類實(shí)現(xiàn)了AIDL接口。在Stub類中有兩個(gè)方法一個(gè)是asInterface()方法,該方法返回的是AIDL文件生成的接口,另外一個(gè)方法是asBinder(),該方法返回的是一個(gè)IBinder類型的實(shí)例。
asInterface()和asBinder()方法非常有用,asInterface()方法可以用于客戶端的IPC方法調(diào)用,另外一個(gè)方法可以用于在服務(wù)端返回Binder實(shí)例,并在服務(wù)端實(shí)現(xiàn)響應(yīng)的接口方法。
上面介紹過(guò)在定義非基本數(shù)據(jù)類型的時(shí)候必須定義數(shù)據(jù)走向,聲明in或out或者inout,在AIDL生成的Java文件中就可以看出來(lái)究竟了,這里可以參看setListData()方法的生成實(shí)現(xiàn)。
@Override public void setListData(java.util.List<java.lang.String> inList, java.util.List<java.lang.String> outList) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStringList(inList); mRemote.transact(Stub.TRANSACTION_setListData, _data, _reply, 0); _reply.readException(); _reply.readStringList(outList); } finally { _reply.recycle(); _data.recycle(); } }
如果聲明數(shù)據(jù)時(shí)是in,在生成相對(duì)應(yīng)的方法的時(shí)候調(diào)用的實(shí)際上是Parcel的writeXXX方法,如果聲明的是out,在實(shí)現(xiàn)上面采用的是readXXX,所以在定義的時(shí)候一定要明確調(diào)用邏輯。
接下來(lái)看一下服務(wù)端中MyService類的實(shí)現(xiàn)。
public class MyService extends Service { private static final String TAG = "AIDL_Server"; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } private IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { Log.d(TAG, "anInt:" + anInt + " aLong:" + aLong + " aBoolean:" + aBoolean + " aFloat:" + aFloat + " aDouble:" + aDouble + " aString:" + aString); } @Override public String getName() throws RemoteException { return "admin"; } @Override public void setListData(List<String> inList, List<String> outList) throws RemoteException { Log.d(TAG, "inList:" + inList.toString()); setOutList(outList); } @Override public void setMapData(Map map) throws RemoteException { Log.d(TAG, "map:" + map.toString()); } @Override public void setUser(User user) throws RemoteException { Log.d(TAG, "user:" + user.toString()); } }; private void setOutList(List<String> list) { list.add("out_01"); list.add("out_02"); list.add("out_03"); } }
在MyService類中,除了getName是一個(gè)有返回值的方法,其余的方法都是void類型的,另外在數(shù)據(jù)走向方面,除了setListData方法的第二個(gè)參數(shù)outList是輸出類型的參數(shù),其余的參數(shù)都是輸入類型參數(shù),所以這里將其它參數(shù)直接打印出來(lái)了。
調(diào)用IPC方法
在客戶端想要調(diào)用Android的AIDL中定義的IPC方法,可以通過(guò)如下步驟實(shí)現(xiàn):
- 首先需要定義一個(gè)相同包名相同目錄的AIDL文件夾;
- 聲明一個(gè)AIDL文件生成的接口實(shí)例;
- 實(shí)現(xiàn)ServiceConnection接口;
- 調(diào)用bindService綁定服務(wù),傳入生成的ServiceConnection實(shí)例;
- 在onServiceConnected()實(shí)現(xiàn)中,將收到的IBinder實(shí)例(名為 service)。調(diào)用 XXX.Stub.asInterface((IBinder)service),以將返回的參數(shù)轉(zhuǎn)換為 AIDL生成的接口類型。
- 通過(guò)調(diào)用生成的AIDL接口實(shí)例中對(duì)應(yīng)的方法就可以實(shí)現(xiàn)IPC調(diào)用了;
- 在不使用的時(shí)候解除服務(wù)的綁定Context.unbindService()。
如下是客戶端Activity中代碼的實(shí)現(xiàn):
public class MainActivity extends AppCompatActivity { private static final String TAG = "AIDL_Client"; private MyConnection conn; private IRemoteService service; private List<String> outList=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void startBind(View v) { Intent intent = new Intent(); conn=new MyConnection(); intent.setAction("com.sunny.server.service.MyService"); bindService(intent, conn, Context.BIND_AUTO_CREATE); } public void startExecute(View v) { try { service.basicTypes(1, 10000L, true, 1.5f, 300.3, "Hello World"); Log.d(TAG, "getName:" + service.getName()); List<String> inList = new ArrayList<String>(); inList.add("inList01"); inList.add("inList02"); service.setListData(inList, outList); Log.d(TAG, "outList:" + outList.toString()); Map<String, String> map = new HashMap<String, String>(); map.put("key01", "value01"); map.put("key02", "value02"); service.setMapData(map); User user = new User(); user.setId(1001); user.setName("admin"); service.setUser(user); } catch (RemoteException e) { e.printStackTrace(); } } private class MyConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder binder) { service = IRemoteService.Stub.asInterface(binder); } public void onServiceDisconnected(ComponentName name) { } } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } }
其它
上述示例只是為了介紹AIDL如何跨進(jìn)程通信的,所以在客戶端接收到的數(shù)據(jù)直接就在主線程中處理了。但是實(shí)際上客戶端調(diào)用服務(wù)端的遠(yuǎn)程方法,被調(diào)用的方法運(yùn)行在服務(wù)端的Binder線程池中的,同時(shí)客戶端線程會(huì)被掛起,這時(shí)候如果服務(wù)端方法執(zhí)行比較耗時(shí),就會(huì)導(dǎo)致客戶端長(zhǎng)時(shí)間阻塞在這里,如果客戶端方法位于UI線程中,可能會(huì)引起ANR。在實(shí)際開(kāi)發(fā)的時(shí)候注意,客戶端進(jìn)行IPC通信的時(shí)候盡量放在子線程中。由于服務(wù)端的方法本身就是運(yùn)行在服務(wù)端的Binder線程池中,所以即使服務(wù)端需要執(zhí)行大量耗時(shí)的工作也不需要開(kāi)啟新的線程去執(zhí)行。
另外一定要注意的就是安全性,默認(rèn)情況下遠(yuǎn)程服務(wù)任何人都可以連接,這應(yīng)該不是我們所需要的,所以還需要考慮一下權(quán)限驗(yàn)證。一般情況下有兩種處理方法,第一種是通過(guò)自定義權(quán)限的方法,我們?cè)诜?wù)端Service方法的onBinder()方法中添加權(quán)限驗(yàn)證,如果權(quán)限驗(yàn)證不通過(guò)直接返回null。另外一種就是在服務(wù)端的onTransact()方法中做驗(yàn)證,也是做權(quán)限驗(yàn)證,如果不通過(guò)直接返回false。除了上面講的權(quán)限驗(yàn)證之外,可以通過(guò)getCallingPid()和getCallingUid()拿到客戶端應(yīng)用的Pid和Uid進(jìn)行校驗(yàn)。
有關(guān)AIDL的介紹就先到這里了,后續(xù)繼續(xù)介紹一下Binder有關(guān)內(nèi)容。以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter實(shí)現(xiàn)自定義篩選框的示例代碼
本文主要介紹了Flutter實(shí)現(xiàn)自定義篩選框的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07使用adb?or?fastboot命令進(jìn)入高通的9008(edl)模式的兩種方法
這篇文章主要介紹了使用adb?or?fastboot命令進(jìn)入高通的9008(edl)模式,兩種方式通過(guò)命令給大家寫(xiě)的非常詳細(xì),文中又給大家補(bǔ)充介紹了高通手機(jī)?進(jìn)入?高通9008模式的兩種方法,需要的朋友可以參考下2023-01-01Java4Android開(kāi)發(fā)教程(三)java基本概念
本文介紹了Java4Android的基本概念,都是開(kāi)發(fā)必備的基礎(chǔ)知識(shí),希望能對(duì)大家有所幫助2014-10-10Android?webview攔截H5的接口請(qǐng)求并返回處理好的數(shù)據(jù)代碼示例
這篇文章主要給大家介紹了關(guān)于Android?webview攔截H5的接口請(qǐng)求并返回處理好的數(shù)據(jù)的相關(guān)資料,通過(guò)WebView的shouldInterceptRequest方法,Android可以攔截并處理WebView中的H5網(wǎng)絡(luò)請(qǐng)求,需要的朋友可以參考下2024-10-10android private libraries 中的包源代碼添加方法
這篇文章主要介紹了android private libraries 中的包源代碼添加方法,方法很簡(jiǎn)單,看完本文即可學(xué)會(huì),需要的朋友可以參考下2015-05-05Android實(shí)現(xiàn)電子羅盤(pán)(指南針)方向傳感器的應(yīng)用
今天小編就為大家分享一篇關(guān)于Android實(shí)現(xiàn)電子羅盤(pán)(指南針)方向傳感器的應(yīng)用,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03Android實(shí)現(xiàn)一個(gè)包含表格的圖標(biāo)庫(kù)實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)一個(gè)包含表格的圖標(biāo)庫(kù)的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01Android?ViewPager2?+?Fragment?聯(lián)動(dòng)效果的實(shí)現(xiàn)思路
這篇文章主要介紹了Android?ViewPager2?+?Fragment?聯(lián)動(dòng),本篇主要介紹一下 ViewPager2 + Fragment聯(lián)動(dòng)效果的實(shí)現(xiàn)思路,需要的朋友可以參考下2022-12-12android真機(jī)調(diào)試時(shí)無(wú)法顯示logcat信息的解決方法介紹
以下是對(duì)android真機(jī)調(diào)試時(shí)無(wú)法顯示logcat信息的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-07-07