使用AIDL
AIDL可以處理并發(fā)請求并且可以實(shí)現(xiàn)跨進(jìn)程調(diào)用服務(wù)端的方法述吸。
實(shí)現(xiàn)步驟
服務(wù)端
- 創(chuàng)建一個(gè)Service用來接受客戶端的連接业簿。
- 創(chuàng)建一個(gè)AIDL文件眉踱,在文件中聲明暴露給客戶端的接口袄简。
- 在Service中實(shí)現(xiàn)這個(gè)AIDL钩骇。
客戶端
- 綁定服務(wù)端的Service豺谈。
- 將服務(wù)端返回的Binder轉(zhuǎn)換成對應(yīng)的AIDL類型郑象。
- 調(diào)用AIDL中聲明的方法。
AIDL注意事項(xiàng)
1. AIDL支持的數(shù)據(jù)類型
- 基本類型茬末。
- String厂榛、CharSequence。
- List丽惭,只支持ArrayList击奶,其中元素必須是AIDL支持的。
- Map责掏,只支持HashMap柜砾,key和value都必須是AIDL支持的他挎。
- 所有實(shí)現(xiàn)了Parcelable的類玲躯。
- AIDL接口本身。
2. AIDL接口和實(shí)現(xiàn)了Parcelable的類一定要顯式import進(jìn)來晕翠。
3. 如果AIDL中用到了Parcelable類瞳浦,那么必須創(chuàng)建一個(gè)和類名相同名字的aidl文件并聲明這個(gè)類為parcelable類型担映。
package com.utte.aidltest;
parcelable Book;
4. AIDL中除了基本類型,其他類型都必須聲明in叫潦、out蝇完、inout表示參數(shù)是輸入型、輸出型還是輸入輸出型的。
void addBook(in Book book);
應(yīng)該根據(jù)實(shí)際來指定參數(shù)類型短蜕,不能一概使用out或者inout泛源,因?yàn)檫@在底層是由開銷的。
5. AIDL接口中只支持聲明方法忿危,不支持聲明靜態(tài)常量达箍。
6. 建議把所有的AIDL相關(guān)的類和文件都放入同一個(gè)包中
當(dāng)客戶端是另外一個(gè)程序時(shí),我們可以把整個(gè)包復(fù)制到客戶端工程中铺厨,比較方便AIDL的開發(fā)缎玫。AIDL的包結(jié)構(gòu)應(yīng)該保持服務(wù)端和客戶端一致,否則會出錯(cuò)解滓。因?yàn)榭蛻舳诵枰葱蛄谢?wù)端中和AIDL接口相關(guān)的所有類赃磨,如果路徑不一樣的話,就無法成功反序列化洼裤。
例子中都是在同一個(gè)項(xiàng)目中的不同進(jìn)程邻辉,所以不需要復(fù)制AIDL文件。
具體步驟
1. 創(chuàng)建AIDL接口
- 創(chuàng)建一個(gè)后綴名為aidl的接口文件腮鞍。
- 在文件中聲明一個(gè)接口和所需要的接口方法值骇。
// IBookManager.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
- 創(chuàng)建聲明類的aidl文件
//Book.aidl
package com.utte.aidltest;
parcelable Book;
- buid一下,自動生成AIDL接口的Binder類移国。
2. 實(shí)現(xiàn)遠(yuǎn)程服務(wù)端Service
- 創(chuàng)建一個(gè)Service吱瘩,并在Menifest中注冊。
<service android:name=".aidl.BookManagerService"
android:process=":rremote"/>
- onCreate中初始化數(shù)據(jù)迹缀。
- 創(chuàng)建一個(gè)AIDL的Binder對象并實(shí)現(xiàn)其接口方法使碾。
- onBind()中返回這個(gè)Binder對象。
public class BookManagerService extends Service {
Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(0, "book"));
mBookList.add(new Book(1, "bbook"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
因?yàn)锳IDL方法是在服務(wù)端線程池中進(jìn)行的祝懂,存在線程同步的問題票摇,可以直接使用CopyOnWriteArrayList來進(jìn)行自動的線程同步,它支持并發(fā)讀寫砚蓬。
注意點(diǎn)中說AIDL只支持ArrayList矢门,雖然這里服務(wù)端中聲明集合為CopyOnWriteArrayList,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)怜械,最終形成一個(gè)ArrayList返回給客戶端颅和,并不影響結(jié)論。
3. 實(shí)現(xiàn)客戶端
- 綁定遠(yuǎn)程Service缕允。
- 將Service返回的IBinder轉(zhuǎn)換成AIDL接口類型。
- 直接使用轉(zhuǎn)換后的Binder調(diào)用遠(yuǎn)程方法蹭越。
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
IBookManager mBinder;
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
Button button = findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
List<Book> bookList = mBinder.getBookList();
Log.d(TAG, "onClick: " + bookList.getClass().getCanonicalName());
for (Book book : bookList) {
Log.d(TAG, "onClick: " + book.bookId + " " + book.bookName);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
其實(shí)直接在UI線程去調(diào)用遠(yuǎn)程方法的寫法是不好的障本,因?yàn)檎{(diào)用遠(yuǎn)程方法是耗時(shí)的,可能會導(dǎo)致ANR。
上面我們還打印了服務(wù)端返回的BookList的類型驾霜,發(fā)現(xiàn)是ArrayList案训。證實(shí)了上面的結(jié)論。
實(shí)現(xiàn)觀察者模式
實(shí)現(xiàn)這樣一個(gè)需求粪糙,每當(dāng)有新書添加到服務(wù)端强霎,服務(wù)端就通知客戶端,并且客戶端可以訂閱也可以取消訂閱蓉冈。
1. 修改AIDL文件
- 新建監(jiān)聽aidl接口
// IOnNewBookArrivedListener.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
- 在IBookManager中增加注冊和解綁監(jiān)聽的兩個(gè)接口方法
// IBookManager.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
import com.utte.aidltest.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
2. 修改服務(wù)端
- 增加一個(gè)監(jiān)聽集合
RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
RemoteCallbackList是一個(gè)接口城舞,支持管理任意AIDL接口。
public class RemoteCallbackList<E extends IInterface>
它的內(nèi)部有一個(gè)map寞酿,值是Callback家夺,鍵是IBinder。Callback就是監(jiān)聽接口伐弹,
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
雖然說跨進(jìn)程傳輸客戶端的通過一個(gè)對象會在服務(wù)端生成不同的對象拉馋,但是這些不同的對象有一個(gè)共同的特點(diǎn)就是它們底層的Binder對象是同一個(gè)。RemoteCallback就利用了這一點(diǎn)惨好。另外RemoteCallback還自動實(shí)現(xiàn)了線程同步煌茴。
- 遍歷監(jiān)聽去通知
private void onNewBookArrived(Book book) {
mBookList.add(book);
int size = mListenerList.beginBroadcast();
for (int i = 0; i < size; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
listener.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
注意RemoteCallback的beginBroadcast()和finishBroadcast()必須配對使用。
- 修改接口方法的實(shí)現(xiàn)
Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
onNewBookArrived(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
RemoteCallback的register()和unregister()方法幫我們封裝了判斷進(jìn)程是否終止日川、對象是否存在景馁、線程同步的工作,我們調(diào)用起來特別簡單逗鸣。
3. 修改客戶端
- 實(shí)現(xiàn)監(jiān)聽接口
IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(final Book newBook) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "onNewBookArrived: " + newBook.bookId + " " + newBook.bookName);
}
});
}
};
服務(wù)端調(diào)用客戶端的onNewBookArrived()合住,這個(gè)方法會在客戶端的Binder線程池中執(zhí)行,如果需要進(jìn)行更新UI的操作撒璧,那么必須要切換線程透葛。
- 調(diào)用訂閱和取消訂閱
case R.id.bt_registe_listener:
mBinder.registerListener(mListener);
break;
case R.id.bt_unregiste_listener:
mBinder.unregisterListener(mListener);
break;
調(diào)用遠(yuǎn)程方法耗時(shí)
- 客戶端調(diào)用遠(yuǎn)程方法時(shí),客戶端線程會被掛起卿樱,如果此服務(wù)端的方法是耗時(shí)的僚害,且此時(shí)客戶端的調(diào)用線程是UI線程,就會導(dǎo)致客戶端ANR繁调。
- onServiceConnection()和onServiceDisconnection()都是運(yùn)行在主線程的萨蚕,所以不要在它們中直接調(diào)用遠(yuǎn)程方法。
- 服務(wù)端的方法本身就運(yùn)行在服務(wù)端的Binder線程池中蹄胰,就算方法耗時(shí)岳遥,也不需要再開線程。
- 服務(wù)端調(diào)用客戶端的listener方法時(shí)也是一樣的裕寨,如果客戶端的方法耗時(shí)浩蓉,就不要在主線程中調(diào)用派继,否則會造成服務(wù)端無法響應(yīng)。
總的來說就是調(diào)用方法的那一端會將當(dāng)前線程掛起捻艳,所以如果方法耗時(shí)驾窟,就不要在主線程中調(diào)用。運(yùn)行調(diào)用方法的那一端不需要新開線程去運(yùn)行认轨,因?yàn)榉椒ㄟ\(yùn)行在那一端的Binder線程池中绅络。
處理Binder死亡
常見有以下兩種方法:
1. 給Binder設(shè)置DeathRecipient
先創(chuàng)建一個(gè)DeathRecipient對象。當(dāng)Binder死亡時(shí)嘁字,系統(tǒng)就會調(diào)用binderDied()恩急,我們可以移除之前綁定的死亡代理對象,并綁定新的服務(wù)拳锚。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBinder == null) {
return;
}
mBinder.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBinder = null;
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
};
2. 在onServiceDisconnected()中重連服務(wù)
@Override
public void onServiceDisconnected(ComponentName name) {
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
兩者區(qū)別在于binderDied()運(yùn)行在客戶端的Binder線程池假栓,更新UI需要切換線程池。而onServiceDisconnected()是在UI線程運(yùn)行的霍掺,可以直接更新UI匾荆。
加入權(quán)限
AIDL中加入權(quán)限驗(yàn)證有兩種方法,但是不只這兩種杆烁。
1. 在onBind中進(jìn)行驗(yàn)證
可以使用permission驗(yàn)證牙丽。
- 在Manifest中聲明所需的權(quán)限。
<permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
- 在服務(wù)端的onBind中驗(yàn)證
@Nullable
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
- 需要權(quán)限的客戶端在Manifest中添加權(quán)限
<uses-permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"/>
2. 在服務(wù)端的onTransact()中驗(yàn)證
記得在分析自動生成的Binder代碼時(shí)兔魂,有說過onTransact()只有返回true才真正的調(diào)用成功烤芦,所以我們可以在這里判斷,如果沒有權(quán)限就返回false析校。系統(tǒng)為我們生成的Binder文件是不能改的构罗,我們可以在服務(wù)端重寫這個(gè)方法。
除了permission還可以使用其它很多方法智玻,比如包名驗(yàn)證遂唧。
Binder mBinder = new IBookManager.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (!packageName.startsWith("com.utte")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
//其他方法......
};
getCallingUid()可以拿到當(dāng)前客戶端所屬的應(yīng)用的uid,通過uid獲取到包名從而進(jìn)行驗(yàn)證吊奢。
之前一直糾結(jié)onTransact()是在服務(wù)端的Binder線程池中運(yùn)行的盖彭,怎么能拿到客戶端的包名。一看才發(fā)現(xiàn)getCallingUid()是Binder的方法页滚。
/**
* Return the Linux uid assigned to the process that sent you the
* current transaction that is being processed. This uid can be used with
* higher-level system services to determine its identity and check
* permissions. If the current thread is not currently executing an
* incoming transaction, then its own uid is returned.
*/
public static final native int getCallingUid();
對著翻譯和原文看了半天召边,大概就是會返回發(fā)送給你正在處理事務(wù)的進(jìn)程的uid,如果當(dāng)前線程沒有正在處理進(jìn)來的事物裹驰,那就返回它自己的uid隧熙。
Binder連接池
如果需要多個(gè)AIDL接口,不可能也像之前一樣一個(gè)AIDL對應(yīng)創(chuàng)建一個(gè)Service并在onBind()中返回邦马,有幾個(gè)AIDL就創(chuàng)建幾個(gè)Service不現(xiàn)實(shí)贱鼻。我們應(yīng)該把所有的AIDL放在同一個(gè)Service中管理宴卖。
- 每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口滋将。
- 向服務(wù)端提供自己的唯一標(biāo)識和對應(yīng)的Binder對象邻悬。
- 服務(wù)端只需要一個(gè)Service提供一個(gè)queryBinder接口返回相應(yīng)的Binder對象給客戶端。
- 客戶端拿到所需的Bindr對象后就可以遠(yuǎn)程調(diào)用方法了随闽。
具體實(shí)現(xiàn)
1. 創(chuàng)建兩個(gè)AIDL接口父丰,并分別單獨(dú)實(shí)現(xiàn)
ICombine AIDL接口。
package com.utte.aidltest.pool;
interface ICombine {
String combine(String strA, String strB);
}
實(shí)現(xiàn)CombineImpl的抽象方法掘宪。
package com.utte.aidltest.pool;
import android.os.RemoteException;
public class CombineImpl extends ICombine.Stub {
@Override
public String combine(String strA, String strB) throws RemoteException {
return new String(strA + strB);
}
}
ICompute.aidl
package com.utte.aidltest.pool;
interface ICompute {
int add(int a, int b);
}
package com.utte.aidltest.pool;
import android.os.RemoteException;
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
2. 提供一個(gè)Binder連接池的AIDL接口
package com.utte.aidltest.pool;
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
Binder連接池的AIDL接口蛾扇,接受具體Binder的code,返回具體的Binder魏滚。
3. 編寫B(tài)inderPool類
BinderPool主要需要實(shí)現(xiàn)如下功能:
- 綁定遠(yuǎn)程Service镀首。
- 實(shí)現(xiàn)IBinderPool這個(gè)AIDL接口。
- 向外暴露queryBinder()方法鼠次。
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_COMBINE = 1;
public static final int BINDER_COMPUTE = 2;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();//獲取context更哄,用于連接Service的
connectBinderPoolService();//連接Service
}
//提供單例的BinderPool
public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
//連接遠(yuǎn)程Service
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
//將上面bindService()變成同步方法。
//等待腥寇,跳至onServiceConnected()成翩,實(shí)現(xiàn)同步獲取連接池的Binder對象
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//從Service拿到Binder連接池的Binder對象
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
//處理斷鏈
mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
//同步結(jié)束,跳回connectBinderPoolService()
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binderDied: ");
mBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
//給客戶端的queryBinder方法
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
//調(diào)用連接池Binder對象的queryBinder()
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
//IBinderPool AIDL接口實(shí)現(xiàn)
public static class BinderPoolImpl extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
//根據(jù)不同的code返回不同的具體業(yè)務(wù)Binder實(shí)現(xiàn)類
switch (binderCode) {
case BINDER_COMBINE:
binder = new CombineImpl();
break;
case BINDER_COMPUTE:
binder = new ComputeImpl();
break;
}
return binder;
}
}
}
中間有使用CountDownLatch來讓bindService()變成同步方法赦役,目的其實(shí)就是讓這個(gè)BinderPool類構(gòu)造器調(diào)用時(shí)就獲取好連接池的Binder對象麻敌。
4. 為IBinderPool編寫Service
public class BinderPoolService extends Service {
private Binder mBinder = new BinderPool.BinderPoolImpl();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
能看到這里的Service非常的簡潔,只需要返回Binder連接池的Binder對象就可以了掂摔。因?yàn)锽inder的實(shí)現(xiàn)分離出去了术羔,并且這個(gè)Service不需要和具體的業(yè)務(wù)Binder直接接觸。
5. 客戶端
客戶端使用BinderPool對象來獲取具體的Binder并調(diào)用方法乙漓。
public class BinderPoolActivity extends AppCompatActivity {
private static final String TAG = "BinderPoolActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_pool);
//另開線程運(yùn)行级历,因?yàn)槭呛臅r(shí)的
new Thread(new Work()).start();
}
private class Work implements Runnable {
@Override
public void run() {
//獲取連接池對象
BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);
ICombine combine = CombineImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMBINE));
ICompute compute = ComputeImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMPUTE));
try {
String combineStr = combine.combine("jtt", "zsz");
int computeInt = compute.add(1, 2);
Log.d(TAG, "doWork: " + combineStr + " " + computeInt);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
如果需要增加新的具體業(yè)務(wù)Binder,只需要新增AIDL簇秒,新增實(shí)現(xiàn)類鱼喉,之后在在BinderPoolImpl中多加一個(gè)case返回對應(yīng)Binder實(shí)現(xiàn)類就可以了。
BinderPool極大提高了AIDL的開發(fā)效率趋观,不需要創(chuàng)建新的Service扛禽,所以建議在開發(fā)工作中多使用BinderPool。