最近在外面面試,屢次被問到跨進程通訊,第一次覺得人家問的是 AIDL 的使用因而簡明扼要的說了句:瞭解,可是沒有在項目中使用過。後來面試的時候這個問題被說起的頻率過高了,因而回來把《Android開發藝術探索》又翻了一遍,此次帶着問題來看書效率確實很高,所以有了本篇文章的總結html
IPC 是Inter-Process Communication
的縮寫,意思是進程間通訊或者說跨進程通訊。通訊就如同咱們寫信、發郵件、打電話、發微信同樣,在代碼實現方式上也有以下幾種:java
既然實現方式達六種之多,那麼像我這種也選擇困難症的患者應該如何來選擇呢?能夠參考下表來選擇適合你本身的業務場景android
名稱 | 優勢 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的數據類型 | 四大組件間的進程間通訊 |
文件共享 | 簡單易用 | 不支持併發,沒法即時通訊 | 無併發訪問、數據簡單且無實時性要求的場景 |
AIDL | 支持一對多併發通訊,支持實時通訊 | 使用複雜,須要自行處理線程同步 | 一對多通訊,且有 RPC 需求 |
Message | 支持一對多串行通訊,支持實時通訊 | 不支持高併發、不支持RPC、只能傳輸Bundle支持的數據類型 | 低併發的一對多即時通訊,無RPC需求或者無須要返回結果的RPC需求 |
ContentProvider | 擅長數據資源訪問,支持一對多併發數據共享,可擴展 | 受約束的AIDL,主要提供數據源的CRDU操做 | 一對多的進程間數據共享 |
Socket | 經過網絡傳輸字節流,支持一對多併發實時通訊 | 實現細節煩瑣,不支持直接的RPC | 網絡數據交換 |
*RPC 是Remote Procedure Call
,意思是跨進程回調的意思git
上面介紹了六種實現方式,接下來進入主題:詳細介紹AIDL的使用。github
AIDL 是 Android Interface Definition Language
的縮寫,意思是Android
接口定義語言,用於讓某個Service
與多個應用程序組件之間進行跨進程通訊,從而能夠實現多個應用程序共享同一個Service
的功能。其使用能夠簡單的歸納爲服務端和客戶端,相似於Socket
同樣,服務端服務於全部客戶端,支持一對多服務。可是服務端如何服務客戶端呢?就像酒店裏的客人入住之後,叫服務員打掃一下衛生,須要按鈴同樣,服務端也須要建立一套本身的響應系統,即 AIDL 接口。 可是這個 AIDL 接口和普通接口不同,其內部僅支持六種數據類型:面試
劉思奇
提醒,short
並不支持)String
和CharSequence
ArrayList
),且集合的每一個元素都必須可以被 AIDL 支持HashMap
),且每一個元素的 key 和 value 都必須被 AIDL 支持Parcelable
的實現類建立過程就不貼圖了,直接上代碼:bash
// JobsInterface.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
// 使用import語句在此聲明任何非默認類型
interface JobsInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
複製代碼
basicTypes
方法是示例,意思是支持的基本數據類型,咱們直接刪除便可,而後添加兩個咱們須要測試的方法:服務器
// JobsInterface.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
// 使用import語句在此聲明任何非默認類型
import com.vincent.keeplive.Offer;
interface JobsInterface {
List<Offer> queryOffers();
void addOffer(in Offer offer);
}
// Offer.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
parcelable Offer;
複製代碼
建立 AIDL 注意事項:微信
parcelable
對象,那麼必須新建一個和它同名的 AIDL 文件,如上面示例。而後在Module的build.gradle中加入下面圖片中的代碼先上代碼再說注意事項:網絡
/**
* 服務端service
*/
class RemoteService : Service() {
private val TAG = this.javaClass.simpleName
private val mList = mutableListOf<Offer>()
private val mBinder = object :JobsInterface.Stub(){
override fun queryOffers(): MutableList<Offer> {
return mList;
}
override fun addOffer(offer: Offer) {
mList.add(offer)
}
}
override fun onCreate() {
super.onCreate()
mList.add(Offer(5000,"智聯招聘"))
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
複製代碼
建立服務端注意事項:
Binder
線程池執行,當服務端一對多時須要考慮方法的同步List
接口(或者 Map
接口),Binder
就會按照List
(或者Map
)的規範去訪問數據並造成 ArrayList
(或者HashMap
) 返回給客戶端。重點是服務端不用考慮本身是什麼List
(Map
)。咱們不生產代碼,咱們只是代碼的搬運工。在這裏,我就直接複製書中的代碼了:
class MainActivity : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
private val mConnection = object :ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val manager = JobsInterface.Stub.asInterface(service)
val list = manager.queryOffers()
Log.e(TAG,"list type:${list.javaClass.canonicalName}")
Log.e(TAG,"queryOffers:${list.toString()}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConnection)
super.onDestroy()
}
}
複製代碼
建立客戶端注意事項:
ArrayList
(HashMap
)類型日誌:
list type:java.util.ArrayList
queryOffers:[[salary:5000, company:智聯招聘]]
複製代碼
以上就是一次完整的 IPC 通訊了,可是這樣的通訊只能是單向的。就好像 APP 只能訪問服務器,而服務器不能訪問 APP 同樣,可是如今人家服務器已經有推送了,咱們的服務端怎麼即時通訊呢?接下來就看看經過觀察者實現即時通訊。
原理就是聲明一個 AIDL 接口,而後在服務端所實現的 AIDL 接口中經過註冊和註銷來添加和刪除聲明的 AIDL 接口。而後在服務端須要發消息給客戶端的時候遍歷全部已註冊的接口來發起通訊。
代碼提及比較枯燥,接下來就經過代碼實戰來看看具體過程吧!
package com.vincent.keeplive.aidl;
import com.vincent.keeplive.aidl.Offer;
// offer 觀察接口
interface IOnNewOfferArrivedInterface {
void onNewOfferArrived(in Offer offer);
}
複製代碼
// JobsInterface.aidl
package com.vincent.keeplive.aidl;
// Declare any non-default types here with import statements
// 使用import語句在此聲明任何非默認類型
import com.vincent.keeplive.aidl.Offer;
import com.vincent.keeplive.aidl.IOnNewOfferArrivedInterface;
interface JobsInterface {
List<Offer> queryOffers();
void addOffer(in Offer offer);
void registerListener(IOnNewOfferArrivedInterface listener);
void unregisterListener(IOnNewOfferArrivedInterface listener);
}
複製代碼
/**
* <p>文件描述:服務端service<p>
* <p>@author 烤魚<p>
* <p>@date 2019/4/14 0014 <p>
* <p>@update 2019/4/14 0014<p>
* <p>版本號:1<p>
*
*/
class RemoteService : Service() {
private val TAG = this.javaClass.simpleName
// offer 容器
private val mList = mutableListOf<Offer>()
// aidl 接口專用容器
private val mListenerList = RemoteCallbackList<IOnNewOfferArrivedInterface>()
private val mBinder = object : JobsInterface.Stub(){
override fun registerListener(listener: IOnNewOfferArrivedInterface?) {
mListenerList.register(listener)
}
override fun unregisterListener(listener: IOnNewOfferArrivedInterface?) {
mListenerList.unregister(listener)
}
override fun queryOffers(): MutableList<Offer> {
return mList;
}
override fun addOffer(offer: Offer) {
mList.add(offer)
// 向客戶端通訊
val size = mListenerList.beginBroadcast()
for (i in 0 until size ){
val listener = mListenerList.getBroadcastItem(i)
listener.onNewOfferArrived(offer)
}
mListenerList.finishBroadcast()
}
}
override fun onCreate() {
super.onCreate()
mList.add(Offer(5000, "智聯招聘"))
}
override fun onBind(intent: Intent): IBinder {
Handler().postDelayed({
mBinder.addOffer(Offer(4500,"51job"))
},1000)
return mBinder
}
}
複製代碼
/**
* 客戶端
*/
class MainActivity : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
var manager:JobsInterface? = null
private val mConnection = object :ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
manager = JobsInterface.Stub.asInterface(service)
val list = manager?.queryOffers()
Log.e(TAG,"list type:${list?.javaClass?.canonicalName}")
Log.e(TAG,"queryOffers:${list.toString()}")
manager?.registerListener(mArrivedListener)
// service?.linkToDeath({
// // Binder 鏈接死亡回調 此處須要重置 manager 併發起重連
// },0)
}
}
private val mArrivedListener = object : IOnNewOfferArrivedInterface.Stub(){
override fun onNewOfferArrived(offer: Offer?) {
Log.e(TAG,"ThreadId:${Thread.currentThread().id} offer:${offer}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
manager?.let {
if(it.asBinder().isBinderAlive){
it.unregisterListener(mArrivedListener)
}
}
unbindService(mConnection)
super.onDestroy()
}
}
複製代碼
RemoteCallbackList
是專門用來處理 AIDL 接口的容器:public class RemoteCallbackList<E extends IInterface>
內部經過ArrayMap來保存客戶端實現的 AIDL 接口:ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
其中的Binder
是客戶端底層傳輸信息的Binder
做爲key
,AIDL 接口做爲value
RemoteCallbackList
沒法像List
同樣操做數據,在獲取元素個數或者註冊、註銷接口的時候須要按照示例操做,其中beginBroadcast
和finishBroadcast
必須配對使用,不然會有異常beginBroadcast() called while already in a broadcast
或者finishBroadcast() called outside of a broadcast
。
RemoteCallbackList
在register
方法中會觸發IBinder.linkToDeath
,在unregister
方法中會觸發IBinder.unlinkToDeath
方法。
即時通訊的注意事項
Binder
線程池,同時客戶端線程會被掛起。服務端方法運行在Binder
線程池當中,能夠執行耗時任務,非必要不建議單獨開起工做線程進行異步任務。同理,當服務端調用客戶端的方法時服務端掛起,被調用的方法運行在客戶端的Binder
線程池,一樣須要注意耗時任務的線程切換ServiceConnection.onServiceDisconnected()
,該方法運行在客戶端的 UI 線程;另外一個是Binder.DeathRecipient.binderDied()
,該方法運行在客戶端的Binder
線程池,不能訪問 UI日誌:
queryOffers:[[salary:5000, company:智聯招聘]]
ThreadId:1262 offer:[salary:4500, company:51job]
複製代碼
默認狀況下,咱們的遠程訪問任何人均可以使用,這不是咱們但願看到的,所以須要添加權限驗證。權限驗證能夠在服務端的onBind()
方法中執行,也能夠在onTransact()
方法中執行,既能夠自定義權限驗證,也能夠經過包名的方式驗證。
示例:
private val mBinder = object : JobsInterface.Stub(){
......
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
// 驗證權限 返回false表明權限未驗證經過
val check = checkCallingOrSelfPermission("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE")
if(check == PackageManager.PERMISSION_DENIED){
return false
}
val packages = packageManager.getPackagesForUid(Binder.getCallingUid())
if(packages != null && packages.size>0){
if(!packages[0].endsWith("keeplive")){
return false
}
}
return super.onTransact(code, data, reply, flags)
}
}
// AndroidManifest
<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
<service android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":jing"
android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
複製代碼
自定義權限注意事項
Intent().setClassName("com.vincent.keeplive","com.vincent.keeplive.RemoteService")
<permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" android:protectionLevel="normal"/>
(被驗證方直接跳過此步驟)<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" />
<service android:name=".RemoteService" android:enabled="true" android:exported="true" android:process=":jing" android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000) }
使用Android studio建立的AIDL編譯時找不到自定義類的解決辦法