前言
對于程序的概念,來到這裡的都是程式設計修仙之人,就不再啰嗦了,相信大家倒着、跳着、躺着、各種姿勢都能背出來。
為什麼要使用多程序,一個程序不就可以了嗎?
相信很多同學在實際開發中,基本都不會去給app劃分程序,而且,在Android中使用多程序,還可能需要編寫額外的程序通訊代碼,還可能帶來額外的Bug,這無疑加大了開發的工作量,在很多創業公司中工期也不允許,這導緻了整個app都在一個程序中。
整個app都在一個程序有什麼弊端?
在Android中,虛拟機配置設定給各個程序的運作記憶體是有限制值的(這個值可以是32M,48M,64M等,根據機型而定),試想一下,如果在app中,增加了一個很常用的圖檔選擇子產品用于上傳圖檔或者頭像,加載大量Bitmap會使app的記憶體占用迅速增加,如果你還把檢視過的圖檔緩存在了記憶體中,那麼OOM的風險将會大大增加,如果此時還需要使用WebView加載一波網頁,我就問你怕不怕!
微信,微網誌等主流app是如何解決這些問題的?
微信移動開發團隊在 《Android記憶體優化雜談》 一文中就說到:“對于webview,圖庫等,由于存在記憶體系統洩露或者占用記憶體過多的問題,我們可以采用單獨的程序。微信目前也會把它們放在單獨的tools程序中”。
下面我們使用adb檢視一下微信和微網誌的程序資訊(Android 5.0以下版本可直接在“設定 -> 應用程式”相關條目中檢視):
進入adb shell後,使用 “ps | grep 條目名稱” 可以過濾出想要檢視的程序。
可以看到,微信的确有一個tools程序,而新浪微網誌也有image相關的程序,而且它們當中還有好些其它的程序,比如微信的push程序,微網誌的remote程序等,這裡可以看出,他們不單單隻是把上述的WebView、圖庫等放到單獨的程序,還有推送服務等也是運作在獨立的程序中的。一個消息推送服務,為了保證穩定性,可能需要和UI程序分離,分離後即使UI程序退出、Crash或者出現記憶體消耗過高等情況,仍不影響消息推送服務。
可見,合理使用多程序不僅僅是有多大好處的問題,我個人認為而且是很有必要的。
是以說,我們最好還是根據自身情況,考慮一下是否需要拆分程序。這也是本文的初衷:給大家提供一個多程序的參考思路,在遇到上述問題和場景的時候,可以考慮用多程序的方法來解決問題,又或者,在面試的時候,跟面試官聊到這方面的知識時候也不至于尴尬。
為什麼需要“跨程序通訊”?
Android的程序與程序之間通訊,有些不需要我們額外編寫通訊代碼,例如:把選擇圖檔子產品放到獨立的程序,我們仍可以使用startActivityForResult方法,将選中的圖檔放到Bundle中,使用Intent傳遞即可。(看到這裡,你還不打算把你項目的圖檔選擇弄到獨立程序麼?)
但是對于把“消息推送Service”放到獨立的程序,這個業務就稍微複雜點了,這個時候可能會發生Activity跟Service傳遞對象,調用Service方法等一系列複雜操作。
由于各個程序運作在相對獨立的記憶體空間,是以它們是不能直接通訊的,因為程式裡的變量、對象等初始化後都是具有記憶體位址的,舉個簡單的例子,讀取一個變量的值,本質是找到變量的記憶體位址,取出存放的值。不同的程序,運作在互相獨立的記憶體(其實就可以了解為兩個不同的應用程式),顯然不能直接得知對方變量、對象的記憶體位址,這樣的話也自然不能通路對方的變量,對象等。此時兩個程序進行互動,就需要使用跨程序通訊的方式去實作。簡單說,跨程序通訊就是一種讓程序與程序之間可以進行互動的技術。
跨程序通訊的方式有哪些?
- 四大元件間傳遞Bundle;
- 使用檔案共享方式,多程序讀寫一個相同的檔案,擷取檔案内容進行互動;
- 使用Messenger,一種輕量級的跨程序通訊方案,底層使用AIDL實作(實作比較簡單,部落客開始本文前也想了一下是否要說一下這個東西,最後還是覺得沒有這個必要,Google一下就能解決的問題,就不啰嗦了);
- 使用AIDL(Android Interface Definition Language),Android接口定義語言,用于定義跨程序通訊的接口;
- 使用ContentProvider,常用于多程序共享資料,比如系統的相冊,音樂等,我們也可以通過ContentProvider通路到;
- 使用Socket傳輸資料。
接下來本文将重點介紹使用AIDL進行多程序通訊,因為AIDL是Android提供給我們的标準跨程序通訊API,非常靈活且強大(貌似面試也經常會問到,但是真正用到的也不多…)。上面所說的Messenger也是使用AIDL實作的一種跨程序方式,Messenger顧名思義,就像是一種串行的消息機制,它是一種輕量級的IPC方案,可以在不同程序中傳遞Message對象,我們在Message中放入需要傳遞的資料即可輕松實作程序間通訊。但是當我們需要調用服務端方法,或者存在并發請求,那麼Messenger就不合适了。而四大元件傳遞Bundle,這個就不需要解釋了,把需要傳遞的資料,用Intent封裝起來傳遞即可,其它方式不在本文的讨論範圍。
下面開始對AIDL的講解,各位道友準備好渡劫了嗎?
使用AIDL實作一個多程序消息推送
像圖檔選擇這樣的多程序需求,可能并不需要我們額外編寫程序通訊的代碼,使用四大元件傳輸Bundle就行了,但是像推送服務這種需求,程序與程序之間需要高度的互動,此時就繞不過程序通訊這一步了。
下面我們就用即時聊天軟體為例,手動去實作一個多程序的推送例子,具體需求如下:
- UI和消息推送的Service分兩個程序;
- UI程序用于展示具體的消息資料,把使用者發送的消息,傳遞到消息Service,然後發送到遠端伺服器;
- Service負責收發消息,并和遠端伺服器保持長連接配接,UI程序可通過Service發送消息到遠端伺服器,Service收到遠端伺服器消息通知UI程序;
- 即使UI程序退出了,Service仍需要保持運作,收取伺服器消息。
實作思路
先來整理一下實作思路:
- 建立UI程序(下文統稱為用戶端);
- 建立消息Service(下文統稱為服務端);
- 把服務端配置到獨立的程序(AndroidManifest.xml中指定process标簽);
- 用戶端和服務端進行綁定(bindService);
- 讓用戶端和服務端具備互動的能力。(AIDL使用)
例子具體實作
為了閱讀友善,下文中代碼将省略非重點部分,可以把本文完整代碼Clone到本地再看文章:
https://github.com/V1sk/AIDL
Step0. AIDL調用流程概覽
開始之前,我們先來概括一下使用AIDL進行多程序調用的整個流程:
- 用戶端使用bindService方法綁定服務端;
- 服務端在onBind方法傳回Binder對象;
- 用戶端拿到服務端傳回的Binder對象進行跨程序方法調用;
AIDL調用過程
整個AIDL調用過程概括起來就以上3個步驟,下文中我們使用上面描述的例子,來逐漸分解這些步驟,并講述其中的細節。
Step1.用戶端使用bindService方法綁定服務端
1.1 建立用戶端和服務端,把服務端配置到另外的程序
- 建立用戶端 -> MainActivity;
- 建立服務端 -> MessageService;
- 把服務端配置到另外的程序 -> android:process=”:remote”
上面描述的用戶端、服務端、以及把服務端配置到另外程序,展現在AndroidManifest.xml中,如下所示:
| |
開啟多程序的方法很簡單,隻需要給四大元件指定android:process标簽。
1.2 綁定MessageService到MainActivity
建立MessageService
此時的MessageService就是剛建立的模樣,onBind中傳回了null,下一步中我們将傳回一個可操作的對象給用戶端。
| |
用戶端MainActivity調用bindService方法綁定MessageService
這一步其實是屬于Service元件相關的知識,在這裡就比較簡單地說一下,啟動服務可以通過以下兩種方式:
- 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags);
- 使用startService方法 -> startService(Intent service);
bindService & startService差別:
使用bindService方式,多個Client可以同時bind一個Service,但是當所有Client unbind後,Service會退出,通常情況下,如果希望和Service互動,一般使用bindService方法,使用onServiceConnected中的IBinder對象可以和Service進行互動,不需要和Service互動的情況下,使用startService方法即可。
正如上面所說,我們是要和Service互動的,是以我們需要使用bindService方法,但是我們希望unbind後Service仍保持運作,這樣的情況下,可以同時調用bindService和startService(比如像本例子中的消息服務,退出UI程序,Service仍需要接收到消息),代碼如下:
| |
Stpe2.服務端在onBind方法傳回Binder對象
2.1 首先,什麼是Binder?
要說Binder,首先要說一下IBinder這個接口,IBinder是遠端對象的基礎接口,輕量級的遠端過程調用機制的核心部分,該接口描述了與遠端對象互動的抽象協定,而Binder實作了IBinder接口,簡單說,Binder就是Android SDK中内置的一個多程序通訊實作類,在使用的時候,我們不用也不要去實作IBinder,而是繼承Binder這個類即可實作多程序通訊。
2.2 其次,這個需要在onBind方法傳回的Binder對象從何而來?
在這裡就要引出本文中的主題了——AIDL
多程序中使用的Binder對象,一般通過我們定義好的 .adil 接口檔案自動生成,當然你可以走野路子,直接手動編寫這個跨程序通訊所需的Binder類,其本質無非就是一個繼承了Binder的類,鑒于野路子走起來麻煩,而且都是重複步驟的工作,Google提供了 AIDL 接口來幫我們自動生成Binder這條正路,下文中我們圍繞 AIDL 這條正路繼續展開讨論(可不能把人給帶偏了是吧