Android 進程間通訊:深入理解 Messenger 的實現細節

2021-02-15 程式設計師好物館

作者簡介:紀喜才(@D_clock愛吃蔥花),94 年的 Android 開發工程師,目前就職於 YY。Diycode 技術社區管理員,熱愛開源並學習開源,熱衷於技術分享,目標是成為一名 T 型開發者。個人博客:http://www.jianshu.com/users/ec95b5891948,Github地址:https://github.com/D-clock。

近一個半月因為工作變動的緣故,忙著交接工作和複習面試。沒有多少時間來寫博客,連一周三次的健身都有幾個星期沒練了,好多同事問我是胖了還是壯了(我迅速就岔開話題了,機智 boy)。上周離職,這周主要在處理一些私事、做些入職準備工作、看點書之類的,下周入職 YY(上周才知道原來大神羅昇陽也在 YY)。好啦,說了這麼多,要開始進入 Messenger 的正題了。

前言

看這篇文章前,需要對 Android 的進程間通訊方式有所了解,不然可能會雲裡霧裡。

從使用 Messenger 說起

Android 上實現 IPC (進程間通訊)的方式有好幾種,其中有一種就是使用 AIDL 方式實現,對使用 AIDL 不了解的童鞋可以看下方的官方文檔(需要梯子)。

https://developer.android.com/guide/components/aidl.html

對於使用 AIDL 方式通訊,其關鍵就在於創建 aidl 文件,系統會自動為 aidl 文件生成相應的 Java 類,其關鍵實現在於生成的 Java 類中。

系統提供了一個更方便我們進行 IPC 的類 —— Messenger,先來看看如何使用 Messenger(熟悉的童鞋完全可以跳過這一部分)。

       private Messenger mSender;        private Messenger mReceiver = new Messenger(new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            Bundle data = msg.getData();            if (data != null) {                String response = data.getString("body");                Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();            }        }    });

public class IPCService extends Service {    private Messenger messenger = new Messenger(new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            try {                Thread.sleep(2 * 1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            Message response = Message.obtain();            Bundle data = new Bundle();            data.putString("body", "response");            response.setData(data);            try {                msg.replyTo.send(response);            } catch (RemoteException e) {                e.printStackTrace();            }        }    });    @Override    public IBinder onBind(Intent intent) {        return messenger.getBinder();    }}

   Intent intent = new Intent(this, IPCService.class);    startService(intent);    bindService(intent, conn, Context.BIND_AUTO_CREATE);    private ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            mSender = new Messenger(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };

   if (mSender != null) {        Message message = Message.obtain();        message.replyTo = mReceiver;        try {             mSender.send(message);        } catch (RemoteException e) {             e.printStackTrace();        }     }

至此,在綁定服務進程初始化 Sender 後,即可以做多進程間的交互工作了。使用 Messenger 來實現多進程的交互相比我們用 aidl 來要方便得多,但是 Messenger 的內部也是採用 aidl 實現的,只不過為了方便開發者調用而進行一些封裝,使得開發者們可以忽略 aidl 的實現細節。簡單的了解了 Messenger 的基本使用後,下面我們就來看看 Messenger 的原始碼,了解一些內部的實現細節。

Messenger 原始碼通讀

Messenger 類位於 android.os 包下,代碼量不是很多,所以看起來難度不大,只有如下這麼幾個方法。

在上面的示例代碼中可以看到,客戶端進程有 Sender 和 Receiver 兩個 Messenger,如果不需要實現服務進程回調客戶端進程,那麼 Receiver 完全可以不要。當需要服務進程回調客戶端進程時,則需要傳入 Receiver 了。由於需要在進程間傳遞 Messenger 對象,那麼 Messenger 類就必須要繼承 Serializable 或者 Parcelable 接口。按照 Android 系統一向的風格,都是偏向於推薦繼承 Parcelable 來實現。

所以,上面看到的 describeContents、writeToParcel 方法和 CREATOR 對象實際上繼承 Parcelable 的實現。不明白的童鞋可以參照 Parcelable 的官方文檔(需要梯子)

https://developer.android.com/reference/android/os/Parcelable.html

而 equals 和 hashCode 方法自然不用多說啦,大家熟悉得很。而 writeMessengerOrNullToParcel 和 readMessengerOrNullFromParcel 這對靜態方法主要是實現 Messenger 在 Parcel 中的讀寫操作的,實現比較簡單,大家參見下面代碼就可以理解了。

當然,Messenger 除了繼承 Parcelable 外,還需要聲明一個同名的 Messenger.aidl 文件,可以在 framework 層源碼下 android.os 包中找到 Messenger.aidl 文件,對於寫過 aidl 的童鞋,肯定不陌生了。

排除掉上面提到的方法,剩下的主要是 Messenger 的兩個構造方法以及 getBinder 和 send 方法。客戶端調用服務進程方法時,是通過 Messenger 中的 send 方法,所以我們先直接看 send 方法中的內部實現

其內部是調用了 mTarget 的 send 方法,那麼 mTarget 又是何物呢?

從上面可以看到 mTarget 是一個 IMessenger 實例,作為 Messenger 唯一的成員變量。 初始化 mTarget 是在 public Messenger(Handler target) 構造函數中,利用 Handler 的 getIMessenger 方法來獲取一個 IMessenger 的實例。

從上圖可以看到 getIMessenger 前面是沒有修飾符的,這樣控制了該方法的作用域僅限於 android.os 包內給其他類使用,我們日常開發是無法使用該方法的,所以在 API 文檔中也沒有提供相應接口文檔。在倒數第二行中可以看到 new MessengerImpl() 並在最後 return 給調用者。所以實際上, mTarget 具體實現是在 MessengerImpl 中。

MessengerImpl 實際上是 Handler 的一個私有內部類,它繼承了 IMessenger.Stub 並實現 send 方法。用過 aidl 的童鞋 IMessenger.Stub 的身影想必就明白了,實際上 IMessenger 就是系統提供的 IMessenger.aidl 文件,而 IMessenger.Stub 就是由 IMessenger.aidl 生成的類。 IMessenger.aidl 在 framework 層代碼的 android.os 包中可以找到,而關於 IMessenger 的 Java 實現,則可以看下面的連結。

http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/os/IMessenger.java/

MessengerImpl 的 send 實現相對也比較簡單,只有兩行代碼

   public void send(Message msg) {        msg.sendingUid = Binder.getCallingUid();        Handler.this.sendMessage(msg);    }

首先是將發起調用的客戶端進程的 Linux Uid 存儲在我們傳入的 Message 對象中,服務進程收到 Message 可以通過 msg.sendingUid 得知發起調用的進程的 Linux Uid。接著通過 Handler 的 sendMessage 方法發送給服務進程,這意味著 Messenger 與服務進程間的操作是串行的,因此,在有並行需求的場景下 Messenger 就不適用了。

了解完了 send 方法後,最後就只剩下 getBinder 了,其內部實現也簡單。

參照前面的示例代碼,這裡主要還是在 Service 的 onBind 方法中返回 Binder 對象給客戶端調用,實現同樣是 IMessenger 的 Java 實現中。

至此,基本上看完了整個 Messenger 的內部代碼,從上面的分析上看,內部實現確實非常簡單,基於 aidl 的基礎上做的封裝實現,又對開發者屏蔽了底層 aidl 的實現細節。當然個人認為有兩點不足之處:

遠程調用的阻塞與非阻塞

Android 系統跨進程通訊的底層實現都是通過 Binder 實現,正常情況下客戶端進程發起一個遠程方法調用的流程大致如下:

1. 客戶端線程發起調用;
2. 客戶端線程將遠程調用操作交給 Binder 線程池,並阻塞等待返回遠程方法執行完畢返回;

這也就意味著,我們不能在客戶端進程的 UI 線程中發起遠程方法調用,不然如果遠程方法執行了耗時操作,客戶端的 UI 線程將會被阻塞,從而造成 ANR 的問題存在。讀者可以自行嘗試自定義 aidl 並發起一個耗時的遠程方法調用進行驗證。但是,如果你使用系統提供的 Messenger ,則不會出現這樣的問題,無論你的遠程方法執行多麼耗時,客戶端 Messenger 發起調用後會繼續執行接下來的代碼,並不會進行阻塞等待。這裡讓我百思不得其解,為什麼呢?前面我們可以看到 Messenger 的 send 方法實現是在 MessengerImpl 中

並且,發送 Messeage 的操作是利用主線程的 Handler ,並沒有其他的異步操作,為何執行的過程中不阻塞?這點我也完全沒有想明白,最後折騰半天無果向任玉剛老師求助才得到答案。原來,默認情況下發起的遠程方法調用都是阻塞式的,但也可以是非阻塞式的。 Messenger 就是採用非阻塞的方式通訊,其關鍵就在於 IMessenger.aidl 的實現

相比平常自定義的 aidl,多了 oneway 的關鍵字,聲明和不聲明 oneway 關鍵字的在於生成 Java 類中一個參數

不聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 0;聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 android.os.IBinder.FLAG_ONEWAY 。

查看 API 文檔即可以看到 FLAG_ONEWAY 的作用就是讓客戶端能夠非阻塞的調用遠程方法,至此真相大白,如果我們自定義的 aidl 也想實現非阻塞的調用,只需聲明 oneway 關鍵字即可。

總結

因為平時並不常用到 oneway,加上文檔提及的很少,唯一有描述的就是下面這段。這次看了 Messenger 的代碼才知道有這麼回事,也是漲姿勢了。

對我的博文感興趣,可以關注我的簡書喲:

http://www.jianshu.com/users/ec95b5891948/latest_articles

了解最新移動開發相關信息和技術,請關注mobilehub公眾微信號(ID: mobilehub)。

相關焦點

  • Android破解實戰:遊戲蜂窩3.21版本破解記錄
    搜索isLogin函數的所有調用修改所有isLogin調用的返回值為「1」,繼續找到下面兩句:\smali\com\mfwfz\game\fengwoscript\model\manager\HeartAndPermManager.smali找到checkRunPerm函數的實現
  • 智慧照明:利用Osram高功率紅外LED實現光源間的族群通訊
    據麥姆斯諮詢報導,澳大利亞Organic Response公司利用緊鄰光源之間的通訊,控制大型照明設施。
  • 最美應用:從 Android 研發工程師的角度之「最美時光」
    platform=2) 這樣一個網站,它會定期推介一些很有意思的app,作為開發者,每次看到很棒的app都會從實現角度進行剖析,想著如果是自己將如何實現呢?因此,就有了這個系列的文章,旨在從技術選型和架構的角度解讀一些有意思的app。最美時光是最美應用團隊出品的一個紀念日app,用於記錄和分享那些重要的日子,我們就先從這個開始吧!使用的開源函數庫1.
  • Windows編程技術:進程隱藏
    實現進程隱藏的方法很多,這次介紹的是一種較為直接的隱藏方式,InlineHOOK。二、實現原理    首先,先來講解下為什麼 HOOK ZwQuerySystemInformation 函數就可以實現指定進程隱藏。
  • 我市:創新加快通訊器材產業發展
    ,實現了通訊器材產品從低端到高端的更新換代。近日,2016年第17屆伊朗電子通訊及創新技術展在伊朗首都德黑蘭盛大舉行。為推動通訊器材產業健康快速發展,市委、市政府立足產業基礎優勢,積極幫助企業轉觀念,調結構,擴規模,不斷催發出華興新銳通信科技有限公司、義博通信設備有限公司等一批科技含量高、創新能力強的領頭企業,帶動了全市通訊器材產業的轉型升級,拓寬了產業發展空間。
  • 新一代Android渠道打包工具:1000個渠道包只需要5秒
    細節處理原理很簡單,就是將渠道信息存放在APK文件的注釋欄位中,但是實現起來遇到不少坑,測試了好多次。解決方法由於使用Java直接寫入和讀取ZIP文件的注釋都不可行,使用Python又不方便與Gradle系統集成,所以只能自己實現注釋的寫入和讀取。實現起來也不複雜,就是為了提高性能,避免讀取整個文件,需要在注釋的最後加入幾個MAGIC字節,這樣從文件的最後開始,讀取很少的幾個字節就可以定位渠道名的位置。
  • 俄羅斯杜馬加快中醫藥進入俄羅斯法律進程
    杜姆索夫在國家杜馬會見出席論壇的中方主要代表時表示,國家杜馬正在與中國全國人大常委會共同開展工作,加快推動中醫藥進入俄羅斯的法律進程。會議呼籲充分學習推廣中醫藥學的寶貴知識和經驗,實現各國傳統醫藥學的合作與互補。
  • 中國電信2015年核心路由器集採:中興通訊T8000中標
    近日,中國電信2015年統談統籤類IP設備集採結果公布,在最重要的核心路由器方面,中興通訊T8000核心路由器中標CR-A1(400G)標段,獲建中國電信城域網大容量核心節點
  • 查處電信網絡詐騙 宜賓6月底前全面實現「三方通話」
    (網絡配圖)目前,隨著現代通信技術和金融服務的不斷發展,利用固定電話、手機、網絡、簡訊實施的通訊網絡詐騙犯罪愈演愈烈,嚴重危害人民群眾財產安全,擾亂正常生產生活秩序,已成為社會各界普遍關注的熱點問題宜賓市自2015年11月1日開展打擊治理電信網絡新型違法犯罪專項行動以來,先後4次組織召開局際聯席會議,採取公安牽頭、部門聯動、考核引導、跟蹤問效,深入推進全市專項行動的開展。2016年9月23日,宜賓市「反通訊網絡詐騙中心」正式掛牌成立運行,有效發揮了涉案帳戶及時查詢、緊急止付、快速凍結、追贓挽損等重大作用。
  • 綜合進程管理器(wmip)
    若要對進程進行操作,請到主界面選擇功能 2。"= 結束所有進程" << endl; cout << ">> 5 = 暫停所有進程的所有線程" << endl; cout << ">> 6 = 恢復所有進程的所有線程" << endl; cout << ">> 7 = 關閉所有進程的所有窗體" << endl; cout
  • Messenger Coffee:值得廣為宣傳嗎?
    https://www.mvnews.org/messenger-coffee-is-it-worth-the-hype/來一杯咖啡奧利奧冰冰樂!
  • Linux ps命令詳解:查看正在運行的進程
    表 1 ps命令輸出信息含義表頭含義USER該進程是由哪個用戶產生的。PID進程的 ID。%CPU該進程佔用 CPU 資源的百分比,佔用的百分比越高,進程越耗費資源。%MEM該進程佔用物理內存的百分比,佔用的百分比越高,進程越耗費資源。VSZ該進程佔用虛擬內存的大小,單位為 KB。RSS該進程佔用實際物理內存的大小,單位為 KB。TTY該進程是在哪個終端運行的。
  • 【科技通訊】猴年馬月粽動員,端午鉅惠,粽情低價!
    「以前許下的各種不靠譜的願望都應該實現了。」O(∩_∩)O哈哈哈~疑問:我們口中的「猴年馬月」終於來了,那麼,猴年馬月的來歷是什麼呢?解答:其實每年都有一個馬月,而且固定在農曆五月。「今年是農曆猴年,陽曆的6月5日至7月3日,為農曆五月,也就是『猴年馬月』。」
  • ​北排客服中心2019年8月月度通訊
    北排客服中心2019年8月月度通訊內容(統計區間:7.23-8.22)1.集團新聞;2.客服大事;3.部室動態;4.
  • 日劇《Leaders》:深入理解豐田精神的生動教材
    由幾百名志願群眾演員支持,再現了當年的悲壯時刻影片以愛知佐一郎的追悼會結束,為佐一郎的創業生涯畫上了句號,也展現了愛知人眾志成城,一定要實現佐一郎遺願的堅強志願。抱歉,由於版權限制,不能再上載更多的片花,大家可以在各視頻網站上搜:日劇 Leaders 先驅)Leaders不僅是一部優秀的電視劇,更是一部豐田史詩,一部學習豐田的形象生動的教材。
  • 大數據助力治霾—賀克斌院士:中國的霧霾汙染治理進程與展望
    日前,在中國生態發展論壇的積極聯絡下,2017˙GIMC大會邀請到中國工程院院士、清華大學環境學院院長、中國生態發展論壇指導委員會主任賀克斌院士來到全球領袖峰會現場,發表了「中國的霧霾汙染治理進程與展望」的演講。未來,中國生態發展論壇還會與長城會在人工智慧技術輔助環境技術創新、環境大數據智能分析等方面有更加深入的合作。
  • 【轉帳】愛爾蘭臉書用戶可以通過messenger境外轉帳啦!
    跨境轉帳一直是海外華人所關心的一個問題,據了解愛爾蘭的facebook用戶今後可通過messenger在歐盟內跨境匯款,該服務由TransferWise
  • NetSpot for Android :在Android上探明附近的WiFi網絡
    您只要在您的Android設備上安裝NetSpot即可輕鬆實現完美的信號覆蓋。這款熱門Android WiFi應用可以掃描並分析周圍存在的全部可用無線網絡。使用我們的WiFi網絡分析儀是增強WiFi網絡表現,實現無與倫比的信號覆蓋,信號強度和性能表現的確切方式。準備好深入了解你的wifi?Android版NetSpot可輕鬆完成WiFi分析。
  • 掌握中考英語閱讀理解:20篇新聞報導類閱讀
    【點評】考查閱讀理解,本文涉及到了細節理解題和正誤判斷題,細節理解題一般可知直接在文中找到答案,正誤判斷題也是細節理解題的一種,也是基於短文內容進行判斷的。3.可知,嫦娥四號的人物包括研究月球表面的太陽風的影響,尋找礦物質,攜帶花種、土豆和蠶卵看,他們是否恩能夠在月球成活,不包括解決地球與月球的通訊問題,故答案是C。  (3)細節理解題,groundbreaking的一是是開創性的,A.有傷害的;B.難以預料的;C.冒險的,D先鋒,故答案是D。