前言
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的使用,包括數據在客戶端和服務端的傳輸,本篇將分析AIDL 回調的使用。
通過本篇文章,你將了解到:
1、跨進程傳輸接口
2、AIDL 回調的使用
3、回調在四大組件里的應用
1、跨進程傳輸接口
跨進程傳遞對象
基本數據類型,如int、short 、String 等不用做任何處理可通過Binder直接傳送。而復雜數據類型,如自定義的類,需要實現Parcelable 接口才能通過Binder傳送。
以之前的獲取學生信息為例:
如上圖所示,客戶端通過IPC 從服務端獲取學生信息,學生信息封裝在Student類里:
public class Student implements Parcelable {
private String name;
private int age;
private float score;
...
}
學生信息包括姓名、年齡、分數三個字段。
我們定義AIDL接口如下:
interface IStudentInfo {
//主動獲取
Student getStudentInfo();
}
客戶端通過調用 getStudentInfo() 方法即可獲取從服務端返回的學生信息。
跨進程傳遞接口
客戶端想要獲取學生信息,需要主動調用 getStudentInfo() 方法。考慮一種場景:
1、學生每一門考試,分數都在變化,客戶端需要一直輪詢去調用getStudentInfo() 方法才能獲取最新的成績。我們知道輪詢是效率比較低的做法,要盡量避免。
2、我們就會想到學生成績發生變化了,服務端就主動通知我們就好啦。
如下圖所示:
現在的問題重點是:服務端如何主動通知客戶端。
依據以往的經驗,有兩種方式可以實現:
1、客戶端通過綁定服務端的Service,進而與服務端通信,那么可以換種思路,客戶端也可以定義Service,而后服務端通過綁定客戶端,進而調用客戶端的接口,主動給客戶端傳遞消息。
2、客戶端綁定了服務端的Service,兩者之間就能夠通信。實際上服務端傳遞了Binder給客戶端,客戶端拿到Binder之后就可以進行通信了,這就說明了Binder對象本身能夠跨進程傳輸。
于是改造之前的接口:
客戶端調用服務端接口的時候將自己生成的Binder傳遞給服務端,那么服務端發生變化的時候就可以通過這個Binder來通知客戶端了。
通過比對1、2兩種方式:
第一種方式過于復雜,對于客戶端、服務端的角色容易搞混。
第二種方式符合我們認知的"回調",也就是說跨進程的回調和同一個進程里的回調理解上是一致的。
2、AIDL 回調的使用
服務端聲明回調接口
定義AIDL 回調接口:
import com.fish.ipcserver.Student;
interface RemoteCallback {
//回調
oneway void onCallback(in Student student);
}
Student 為學生信息類,該對象支持跨進程傳輸。
in 表示數據流方向,表示該Student 對象傳遞給客戶端。
oneway 表示調用onCallback(xx) 方法的線程立即返回,不阻塞等待方法調用結果。
服務端暴露注冊回調接口方法
服務端定義了回調接口,客戶端需要給服務端傳遞接口的實現。因此服務端還需要將注冊回調的接口暴露給客戶端。
定義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() {
//學生成績發生改變
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、服務端發生變化的時候通過RemoteCallback 通知客戶端數據已經發生改變。
客戶端編寫調用邏輯
分三步:
(1)、客戶端綁定服務端Service。
(2)、建立連接后客戶端將IBinder 轉化為IStudentInfo 接口,并注冊回調。
(3)、客戶端處理回調內容。
來看看代碼實現:
(1)綁定服務
//參數1:運行遠程服務的包名
//參數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
先來看看客戶端表現:
步驟如下:
1、當點擊按鈕時,客戶端判斷沒有連接上服務端,于是開始連接。
2、連接成功后,開始注冊服務端接口。
3、再次點擊按鈕時,通過getStudentInfo()方法主動獲取學生信息。
再來看服務端表現:
步驟如下:
1、服務端收到客戶端綁定請求。
2、服務端收到客戶端注冊的回調接口。
3、服務端點擊按鈕改變學生分數,并通過回調接口通知客戶端。
4、客戶端收到后彈出Toast。
通過以上兩個測試效果可以看出,客戶端不僅能夠主動調用服務端方法,同時也可以通過回調監聽服務端的變化。
注意事項
1、自定義類型Student.java 與Student.aidl 需要在同一個包名下。
2、客戶端與服務端定義的aidl 文件需要在同一個包名下。通常來說,一般先定義服務端aidl 接口,最后將這些aidl文件拷貝到客戶端相同包名下。
3、bindService Intent 需要指定ComponentName。
3、回調在四大組件里的應用
以ContentProvider 為例:
想要獲取相冊數據,可以通過ContentProvider獲取,而相冊是公共的存儲圖片區域,其它App都可以往里面插入數據或者刪除數據。
而系統也提供了監聽相冊變化的回調:
Handler handler = new Handler(Looper.getMainLooper());
ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
//數據變化回調
super.onChange(selfChange);
}
};
getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver);
如上,通過registerContentObserver(xx)向系統(服務端)注冊了回調接口,當有數據變化的時候服務端會調用onChange(xx)通知客戶端。
不僅ContentProvider 運用到了回調,Service、Activity、Broadcast也用到了。
理解了進程間的回調原理及其使用,對理解四大組件的通信幫助很大。
下篇將重點分析四大組件的框架。
本文基于Android 10.0
完整代碼演示 若是有幫助,給github 點個贊唄~
您若喜歡,請點贊、關注,您的鼓勵是我前進的動力
持續更新中,和我一起步步為營系統、深入學習Android/Java
1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執行原因
8、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發系列不再疑惑
16、Java 線程池系列