前言
IPC 系列文章:
建議按順序閱讀。
Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎
Android IPC 之Binder應用
Android IPC 之AIDL應用(上)
Android IPC 之AIDL應用(下)
Android IPC 之Messenger 原理及應用
Android IPC 之服務端回調
Android IPC 之獲取服務(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)
前面幾篇文章詳細分析了AIDL的使用傻粘,包括數(shù)據(jù)在客戶端和服務端的傳輸篡帕,本篇將分析AIDL 回調的使用。
通過本篇文章,你將了解到:
1、跨進程傳輸接口
2、AIDL 回調的使用
3寓调、回調在四大組件里的應用
1、跨進程傳輸接口
跨進程傳遞對象
基本數(shù)據(jù)類型锄码,如int夺英、short 、String 等不用做任何處理可通過Binder直接傳送滋捶。而復雜數(shù)據(jù)類型痛悯,如自定義的類,需要實現(xiàn)Parcelable 接口才能通過Binder傳送炬太。
以之前的獲取學生信息為例:
如上圖所示灸蟆,客戶端通過IPC 從服務端獲取學生信息,學生信息封裝在Student類里:
public class Student implements Parcelable {
private String name;
private int age;
private float score;
...
}
學生信息包括姓名亲族、年齡炒考、分數(shù)三個字段。
我們定義AIDL接口如下:
interface IStudentInfo {
//主動獲取
Student getStudentInfo();
}
客戶端通過調用 getStudentInfo() 方法即可獲取從服務端返回的學生信息霎迫。
跨進程傳遞接口
客戶端想要獲取學生信息斋枢,需要主動調用 getStudentInfo() 方法≈考慮一種場景:
1瓤帚、學生每一門考試描姚,分數(shù)都在變化,客戶端需要一直輪詢去調用getStudentInfo() 方法才能獲取最新的成績戈次。我們知道輪詢是效率比較低的做法轩勘,要盡量避免。
2怯邪、我們就會想到學生成績發(fā)生變化了绊寻,服務端就主動通知我們就好啦。
如下圖所示:
現(xiàn)在的問題重點是:服務端如何主動通知客戶端悬秉。
依據(jù)以往的經驗澄步,有兩種方式可以實現(xiàn):
1、客戶端通過綁定服務端的Service和泌,進而與服務端通信村缸,那么可以換種思路,客戶端也可以定義Service武氓,而后服務端通過綁定客戶端梯皿,進而調用客戶端的接口,主動給客戶端傳遞消息聋丝。
2索烹、客戶端綁定了服務端的Service工碾,兩者之間就能夠通信弱睦。實際上服務端傳遞了Binder給客戶端,客戶端拿到Binder之后就可以進行通信了渊额,這就說明了Binder對象本身能夠跨進程傳輸况木。
于是改造之前的接口:
客戶端調用服務端接口的時候將自己生成的Binder傳遞給服務端,那么服務端發(fā)生變化的時候就可以通過這個Binder來通知客戶端了旬迹。
通過比對1火惊、2兩種方式:
第一種方式過于復雜,對于客戶端奔垦、服務端的角色容易搞混屹耐。
第二種方式符合我們認知的"回調",也就是說跨進程的回調和同一個進程里的回調理解上是一致的椿猎。
2惶岭、AIDL 回調的使用
服務端聲明回調接口
定義AIDL 回調接口:
import com.fish.ipcserver.Student;
interface RemoteCallback {
//回調
oneway void onCallback(in Student student);
}
Student 為學生信息類,該對象支持跨進程傳輸犯眠。
in 表示數(shù)據(jù)流方向按灶,表示該Student 對象傳遞給客戶端。
oneway 表示調用onCallback(xx) 方法的線程立即返回筐咧,不阻塞等待方法調用結果鸯旁。
服務端暴露注冊回調接口方法
服務端定義了回調接口噪矛,客戶端需要給服務端傳遞接口的實現(xiàn)。因此服務端還需要將注冊回調的接口暴露給客戶端铺罢。
定義AIDL 文件如下:
import com.fish.ipcserver.Student;
import com.fish.ipcserver.RemoteCallback;
interface IStudentInfo {
//主動獲取
Student getStudentInfo();
//注冊回調
oneway void register(in RemoteCallback callback);
}
至此艇挨,服務端提供了兩個方法:
1、getStudentInfo() 客戶端調用此方法主動獲取學生信息韭赘。
2雷袋、register(xx) 客戶端調用此方法注冊回調實例。
服務端編寫回調邏輯
public class StudentService extends Service {
private Student student;
private RemoteCallback remoteCallback;
private MyStudent myStudent;
@Override
public void onCreate() {
super.onCreate();
student = new Student();
student.setAge(19);
student.setName("小明");
myStudent = new MyStudent();
}
class MyStudent extends IStudentInfo.Stub {
@Override
public Student getStudentInfo() throws RemoteException {
return student;
}
@Override
public void register(RemoteCallback callback) throws RemoteException {
//客戶端注冊的回調實例保存到成員變量 remoteCallback
remoteCallback = callback;
}
public void changeScore() {
//學生成績發(fā)生改變
student.setScore((float)(Math.random() * 100));
try {
if (remoteCallback != null)
//調用回調實例方法辞居,將變化后的學生信息傳遞給客戶端
remoteCallback.onCallback(student);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//將Stub 返回給客戶端
return myStudent.asBinder();
}
}
可以看出楷怒,聲明了IStudentInfo 實例。
小結上面的邏輯:
1瓦灶、服務端聲明了Stub(樁鸠删,實際上是Binder實例),并將Stub返回給客戶端贼陶。
2刃泡、客戶端收到Stub(實際上是BinderProxy),然后轉換為IStudentInfo 接口碉怔。而該接口里聲明了兩個方法烘贴,分別是getStudentInfo()和register(xx)。
3撮胧、客戶端調用register(RemoteCallback) 將回調注冊(傳遞)給服務端桨踪。
4、服務端發(fā)生變化的時候通過RemoteCallback 通知客戶端數(shù)據(jù)已經發(fā)生改變芹啥。
客戶端編寫調用邏輯
分三步:
(1)锻离、客戶端綁定服務端Service。
(2)墓怀、建立連接后客戶端將IBinder 轉化為IStudentInfo 接口汽纠,并注冊回調。
(3)傀履、客戶端處理回調內容虱朵。
來看看代碼實現(xiàn):
(1)綁定服務
//參數(shù)1:運行遠程服務的包名
//參數(shù)2:遠程服務全限定類名
ComponentName componentName = new ComponentName("com.fish.ipcserver", "com.fish.ipcserver.StudentService");
Intent intent = new Intent();
intent.setComponent(componentName);
//綁定遠程服務
v.getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
(2)IBinder 轉換為IStudentInfo 接口
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isConnected = true;
//轉為對應接口
iStudentInfo = IStudentInfo.Stub.asInterface(service);
try {
//注冊回調
iStudentInfo.register(remoteCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnected = false;
}
};
(3)客戶端處理回調
//聲明回調
RemoteCallback remoteCallback = new RemoteCallback.Stub() {
@Override
public void onCallback(Student student) throws RemoteException {
Log.d("fish", "call back student:" + student);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(IPCActivity.this, "client receive change:" + student.toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
此處收到服務端的回調后,僅僅Toast 學生信息钓账。
測試效果
為了更貼近實際應用效果碴犬,客戶端、服務端分別跑在不同的App里官扣。
客戶端的應用名為:AndroidDemo
服務端的應用名為:IPCServer
先來看看客戶端表現(xiàn):
步驟如下:
1翅敌、當點擊按鈕時,客戶端判斷沒有連接上服務端惕蹄,于是開始連接蚯涮。
2治专、連接成功后,開始注冊服務端接口遭顶。
3张峰、再次點擊按鈕時,通過getStudentInfo()方法主動獲取學生信息棒旗。
再來看服務端表現(xiàn):
步驟如下:
1喘批、服務端收到客戶端綁定請求。
2铣揉、服務端收到客戶端注冊的回調接口饶深。
3、服務端點擊按鈕改變學生分數(shù)逛拱,并通過回調接口通知客戶端敌厘。
4、客戶端收到后彈出Toast朽合。
通過以上兩個測試效果可以看出俱两,客戶端不僅能夠主動調用服務端方法,同時也可以通過回調監(jiān)聽服務端的變化曹步。
注意事項
1宪彩、自定義類型Student.java 與Student.aidl 需要在同一個包名下。
2讲婚、客戶端與服務端定義的aidl 文件需要在同一個包名下尿孔。通常來說,一般先定義服務端aidl 接口磺樱,最后將這些aidl文件拷貝到客戶端相同包名下纳猫。
3、bindService Intent 需要指定ComponentName竹捉。
3、回調在四大組件里的應用
以ContentProvider 為例:
想要獲取相冊數(shù)據(jù)尚骄,可以通過ContentProvider獲取块差,而相冊是公共的存儲圖片區(qū)域,其它App都可以往里面插入數(shù)據(jù)或者刪除數(shù)據(jù)倔丈。
而系統(tǒng)也提供了監(jiān)聽相冊變化的回調:
Handler handler = new Handler(Looper.getMainLooper());
ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
//數(shù)據(jù)變化回調
super.onChange(selfChange);
}
};
getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver);
如上憨闰,通過registerContentObserver(xx)向系統(tǒng)(服務端)注冊了回調接口,當有數(shù)據(jù)變化的時候服務端會調用onChange(xx)通知客戶端需五。
不僅ContentProvider 運用到了回調鹉动,Service、Activity宏邮、Broadcast也用到了泽示。
理解了進程間的回調原理及其使用缸血,對理解四大組件的通信幫助很大。
下篇將重點分析四大組件的框架械筛。
本文基于Android 10.0
完整代碼演示 若是有幫助捎泻,給github 點個贊唄~
您若喜歡,請點贊埋哟、關注笆豁,您的鼓勵是我前進的動力
持續(xù)更新中,和我一起步步為營系統(tǒng)赤赊、深入學習Android/Java
1闯狱、Android各種Context的前世今生
2、Android DecorView 必知必會
3抛计、Window/WindowManager 不可不知之事
4扩氢、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務
6爷辱、Android invalidate/postInvalidate/requestLayout 徹底厘清
7录豺、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅動Handler-Message-Looper解析
9饭弓、Android 鍵盤一招搞定
10双饥、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12弟断、Android Activity創(chuàng)建到View的顯示過
13咏花、Android IPC 系列
14、Android 存儲系列
15阀趴、Java 并發(fā)系列不再疑惑
16昏翰、Java 線程池系列