Android IPC 之服務端回調

前言

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、跨進程傳輸接口

跨進程傳遞對象

image.png

基本數(shù)據(jù)類型锄码,如int夺英、short 、String 等不用做任何處理可通過Binder直接傳送滋捶。而復雜數(shù)據(jù)類型痛悯,如自定義的類,需要實現(xiàn)Parcelable 接口才能通過Binder傳送炬太。

以之前的獲取學生信息為例:


image.png

如上圖所示灸蟆,客戶端通過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ā)生變化了绊寻,服務端就主動通知我們就好啦。

如下圖所示:


image.png

現(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):

tt0.top-671071.gif

步驟如下:
1翅敌、當點擊按鈕時,客戶端判斷沒有連接上服務端惕蹄,于是開始連接蚯涮。
2治专、連接成功后,開始注冊服務端接口遭顶。
3张峰、再次點擊按鈕時,通過getStudentInfo()方法主動獲取學生信息棒旗。

再來看服務端表現(xiàn):

tt0.top-354096.gif

步驟如下:
1喘批、服務端收到客戶端綁定請求。
2铣揉、服務端收到客戶端注冊的回調接口饶深。
3、服務端點擊按鈕改變學生分數(shù)逛拱,并通過回調接口通知客戶端敌厘。
4、客戶端收到后彈出Toast朽合。

通過以上兩個測試效果可以看出俱两,客戶端不僅能夠主動調用服務端方法,同時也可以通過回調監(jiān)聽服務端的變化曹步。

注意事項

1宪彩、自定義類型Student.java 與Student.aidl 需要在同一個包名下。
2讲婚、客戶端與服務端定義的aidl 文件需要在同一個包名下尿孔。通常來說,一般先定義服務端aidl 接口磺樱,最后將這些aidl文件拷貝到客戶端相同包名下纳猫。
3、bindService Intent 需要指定ComponentName竹捉。

image.png

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 線程池系列

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刘急,隨后出現(xiàn)的幾起案子棚菊,更是在濱河造成了極大的恐慌,老刑警劉巖叔汁,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件统求,死亡現(xiàn)場離奇詭異,居然都是意外死亡据块,警方通過查閱死者的電腦和手機码邻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來另假,“玉大人像屋,你說我怎么就攤上這事”呃海” “怎么了己莺?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵奏甫,是天一觀的道長。 經常有香客問我篇恒,道長扶檐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任胁艰,我火速辦了婚禮款筑,結果婚禮上,老公的妹妹穿的比我還像新娘腾么。我一直安慰自己奈梳,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布解虱。 她就那樣靜靜地躺著攘须,像睡著了一般。 火紅的嫁衣襯著肌膚如雪殴泰。 梳的紋絲不亂的頭發(fā)上于宙,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音悍汛,去河邊找鬼捞魁。 笑死,一個胖子當著我的面吹牛离咐,可吹牛的內容都是我干的谱俭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宵蛀,長吁一口氣:“原來是場噩夢啊……” “哼昆著!你這毒婦竟也來了?” 一聲冷哼從身側響起术陶,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凑懂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瞳别,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體征候,經...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年祟敛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兆解。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡馆铁,死狀恐怖,靈堂內的尸體忽然破棺而出锅睛,到底是詐尸還是另有隱情埠巨,我是刑警寧澤历谍,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站辣垒,受9級特大地震影響望侈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜勋桶,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一脱衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧例驹,春花似錦捐韩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屎债,卻和暖如春仅政,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盆驹。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工圆丹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人召娜。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓运褪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玖瘸。 傳聞我的和親對象是個殘疾皇子秸讹,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容