初學安卓framework系列 三 (系統服務的設計思路)

語言: CN / TW / HK

Client/Server架構,也就是大家最常聽到的C/S架構在後端開發中可以說是祖師爺級別的概念了。其實安卓的系統服務的設計也是遵循C/S架構的,甚至和最近很流行的微服務micro service架構也有關聯。

今天我想用通俗一點的語言稍微講解一下安卓端的CS架構,每個元件設計的理念,讓大家更清晰的理解一下安卓的客戶端伺服器到底是個什麼東西。

Android的C/S 的客戶端

簡單的說,安卓的APP應用層可以當做是安卓C/S架構中的客戶端。而安卓系統中,執行在系統程序system process的系統服務system service,被認為是伺服器端。

Manager API

客戶端除了app本身之外,還包括安卓SDK的客戶端公有API (public API)。基本上sdk裡面帶Manager字樣的類,與之相對應的都會有一個安卓伺服器端的實現。我們先拋開實現細節,教教大家怎麼尋找客戶端對應的伺服器實現。

比如可以讓app在執行時獲取使用者手機SIM卡等相關資訊的TelephonyManager,大家可以看到它的其中和一個方法,用來獲取手機當前的位置的方法。

public CellLocation getCellLocation() { try { Bundle bundle = getITelephony().getCellLocation(); return CellLocation.newFromBundle(bundle); } catch (RemoteException ex) { return null; }

它的呼叫了getITelehony()來獲取一個ITelephony物件,大家一看這種帶I開頭的類就知道肯定要涉及IPC,跨程序呼叫了,它的伺服器實現必然會繼承ITelephony類的Stub。

所以在搜尋該Manager的伺服器端的實現的時候,直接搜尋extends ITelephony的類就好。

``` public class PhoneInterfaceManager extends ITelephony.Stub {

@Override public CellIdentity getCellLocation(String callingPackage, String callingFeatureId) { ..... }

}

```

果不其然,整個framework就只有PhoneInterfaceManager 繼承了該ITelephony類的Stub,並且也可以找打之前getCellLocation 的具體實現。

Screenshot 2022-10-08 at 11.09.03 AM.png 當然其他的各種manager也是照著這樣的方式一一實現的。整個安卓的客戶端public API都有與之對應的實現,都是實現以I開頭的Stub類。

這個小節我先簡單的教大家怎麼找到manager類的對應伺服器端的實現,至於安卓是具體怎麼實現這樣的一對一對映我們接下來的小節再詳細說明。

Android SDK

大家都知道編譯一個安卓app需要android sdk,但是不少人沒搞明白這個SDK到底是編譯時用的還是執行時用的。

解答這個問題其實很簡單,大家開啟你的android studio,隨便點選一個manager類的原始碼,就可以看出來。

Screenshot 2022-10-08 at 11.23.55 AM.png

很明顯,這些SDK裡面的類都不會最終打包進app的APK檔案裡面,如果真的用了這些類當做實現,執行時肯定都會crash(畢竟這裡面全都是拋RuntimeException)。所以SDK的類,都是沒有具體實現的,我們把它們叫Stub類。他們只是用來幫助我們進行編譯而已,目的是讓app開發者能成功打包編譯出APK。這個和gradle裡面的compileOnly 關鍵字很像。而且這樣設計也很有道理,畢竟如果每個app都需要把android framework的公有api類都打包進APK,那每個app安裝檔案都將變得巨大無比。

dependencies { compileOnly 'javax.servlet:servlet-api:2.5' }

但是在之前的圖裡,我明明還是把Manager類畫在了APP的程序裡面,也就是說執行時我們還是需要Manager類的。那這些類到底從哪來的呢?

zygote android

關於Zygote的細節我們就不聊那麼多,網上資源挺多的。但是我們需要知道的重點就是,每一個APP都是Zygote複製出來的一個程序(具體的實現應該是linux裡面的fork),這個程序Dalvik(或者是ART)都是一個獨立的虛擬機器,virtual machine。在最原始的程序裡面,所有的public API, 也就是那些客戶端的Manager類都已經被載入進該程序(虛擬機器)了。所以當每個app被啟動的時候,啟動APP的dalvik或者ART已經載入過了所有的客戶端API的類。這樣,app在執行時就不會找不到對應的Manager類 (symbol not found),但是同時通過android的stub sdk又減少了apk大小,可以說是一舉兩得。

Screenshot 2022-10-08 at 11.48.02 AM.png

Hidden API

最後關於客戶端再分享一個小知識,就是關於隱藏API。安卓的很多public manager class會有自己的隱藏api,可以看原始碼有沒有帶@hide的字樣。比如AudioManager的getMasterStreamType方法,註釋裡面就加了hide

/** * Get the stream type whose volume is driving the UI sounds volume. UI sounds are screen lock/unlock, camera shutter, key clicks... @hide */ public int getMasterStreamType() { }

加了這個註釋之後,安卓在編譯Stub sdk 的時候,就會把帶這個註釋的方法從stub類裡面刪掉,這樣導致app開發者在編譯階段找不到該方法,從而達到隱藏的效果。大家通過android studio開啟AudioManager的stub 就可以發現。

Screenshot 2022-10-08 at 11.58.15 AM.png

所以該隱藏也只是編譯階段隱藏而已,並沒有做到真正的執行時隱藏。

也正因為該隱藏方式是在客戶端的編譯階段隱藏,而且在執行時的dalvik和art虛擬機器裡面還是有對應類被載入,所以在執行時App還是可以通過java的反射來進行訪問。。。。安全性並沒有那麼高

有一些安全性要求比較高的API,會強制在伺服器端run time檢查呼叫者的程序ID,嚴格控制呼叫者身份。比如ActivityManager裡面的一個方法,會檢查callingUId。

void enforceShellRestriction(String restriction, int userHandle) { if (Binder.getCallingUid() == Process.SHELL_UID) { if (userHandle < 0 || mUserManager.hasUserRestriction(restriction, userHandle)) { throw new SecurityException("Shell does not have permission to access user " + userHandle); } } }

這個思想其實和真正的後端開發就很相似了,你想在客戶端做安全性驗證,怎麼也會出現漏洞。比如遊戲客戶端可以用很多種方式繞過安全檢查,或者甚至修改遊戲端執行時程式碼等等方式作弊。

Android的C/S 的伺服器端

說了這麼多客戶端的實現。是時候聊聊伺服器端的實現了。伺服器端的具體實現,我推薦大家看看這篇文章 如何實現一個 System Services?,這是博主吳小龍寫的,步驟非常詳細,大家可以先過一遍有個印象。

不過再多的細節,伺服器端的實現繞不過這三個概念 1. AIDL 2. Bound Service 3. ServiceManager and SystemService

對AIDL的理解

大部分朋友應該對AIDL怎麼用已經很瞭解了,這次我就不講具體怎麼用,而是應該怎麼理解AIDL的設計初衷。

其實我們站在開發者的角度就可以很容易理解。當我的APP需要進行跨程序通, 我作為開發者肯定希望有一個方便的客戶端API,可以直接呼叫該API就間接的和system process通訊.而不需要寫一些非常複雜的C++的程式碼,比如我們總不能要求app開發者寫jni檔案去呼叫linux的系統呼叫來做這個事情。

站在伺服器,也就是系統開發者的角度,我肯定希望當客戶端呼叫我的某一個方法的時候,我不需要做額外的資料的序列化和反序列化。或者通俗一點說,就是客戶端的呼叫的API的方法名,和我伺服器端的方法名一致,介面長得樣子要一樣。 比如在客戶端TelephonyManager#isEmergencyNumber呼叫了ITelephony#isEmergencyNumber, 那麼伺服器端我也希望被呼叫伺服器端實現的isEmergencyNumber方法。

AIDL機制就很好的解決了這個問題。AIDL可以幫助客戶端和系統服務端開發者自動生成程式碼,包裝了一些真正的跨程序通訊的實現,讓開發者不需要自己寫linux的系統呼叫,同時還可以保持雙方介面的一致(方法名一樣等等)。

所以這一套設計方案說到底,就是為了開發者,尤其是app開發者更方便的開發而已。它的出現讓跨程序變得更方便,但是這個跨程序也是在現有框架下進行,比如,客戶端app開發要想和系統進行通訊,必須通過系統現有的service與manager API進行通訊,也還是有一定的限制。

Service的註冊

實現了一個系統級別的服務,總得先生成該服務的例項,並且儲存在某處(確保不會被垃圾回收♻️),才能保證客戶端能正常的呼叫。

安卓的ServiceManager就提供了這樣的功能。

``` @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)

public static void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority) { try { getIServiceManager().addService(name, service, allowIsolated, dumpPriority); } catch (RemoteException e) { Log.e(TAG, "error in addService", e); } } ```

這個方法最後會呼叫一個C++的底層程式碼,把服務例項註冊。當前端的Manager api呼叫ServiceStub的方法的時候,安卓系統會通過IPC呼叫這些註冊了的Service例項。

Android的SystemService

伺服器端的服務可以以Service的的形式存在,也可以以SystemService的形式存在。 Service就不用多說了,大家都知道怎麼做bind service。

SystemService就不太一樣了,大家可以看看這個類是個啥。

``` @SystemApi(client = Client.SYSTEM_SERVER)
public abstract class SystemService {

public void onBootPhase(@BootPhase int phase) {}

} ```

這個抽象類並不以Service的形式存在,而是一個簡單的類。它唯一不一樣的是提供了很多和啟動週期相關的回撥(boot phase).很多朋友不太懂什麼事啟動週期,其實很好理解,通俗一點就是,安卓裝置被設定了螢幕鎖之後,裝置啟動之後會被分成不同的啟動階段。

比如裝置啟動但是沒有解鎖是一個階段,裝置被解鎖之後是另一個階段。每個階段開始之後,安卓的system server會啟動不同的程序。最明顯的例子是打電話的程序,裝置在沒有解鎖之前也需要能接受或者撥打緊急電話。

SystemService類暴露了這樣一個回撥就用意很明顯了,這樣可以讓不同的系統服務在不同的啟動階段做不同的事情。

比如安卓監控裝置溫度的系統服務,他只有在ActivityManager被載入之後才進行一系列業務邏輯的執行

``` class ThermalManagerService extends SystemService {

@Override public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { 
        onActivityManagerReady(); 
    }
}

} ```

Android的SystemServer

那麼有了這麼多服務,在哪裡開始執行呢。就在SystemServer裡面

SystemServer.java的核心程式碼在這幾行

try { t.traceBegin("StartServices"); startBootstrapServices(t); startCoreServices(t); startOtherServices(t); startApexServices(t); }

BootStrap service就是指我們上一節說到的SystemService,這些物件和bootstrap也就是啟動週期緊密的相關,所以都是SystemService。 Core Service就是系統需要的但是對bootstrap不敏感的服務。但是無論是哪種service最後都毫無例外的將自己註冊在ServiceManager了

ServiceManager.addService("sec_key_att_app_id_provider", new KeyAttestationApplicationIdProviderService(context));

就這樣,安卓系統成功的建立了所有需要的服務的例項,並且將其註冊了起來。到此為止,客戶端已經可以嘗試通過Manager API對這些系統服務進行IPC通訊了。

而且所有的這些Service的例項都被儲存在一個List裡面

``` class SystemServiceManager {

private List mServices;

} ```

所以並不用擔心這些Service被垃圾回收。

到這裡基本上安卓的前端和後端我們就都瞭解了。但是這些都只是一些概念的皮毛,有很多細節還沒覆蓋到。大家有興趣可以再多查查資料。

不過,安卓在架構上也在不斷的進化。一個例子:很多後端的開發概念,微服務現在在安卓的架構中也開始被支援了。

Android自己的微服務-APEX

從安卓10開始,android 的系統開發者已經開始嘗試把很多framework中的程式碼模組化。從framework/base移動到packages/modules/下面,變成獨立的模組,從而將該模組編譯成獨立的APEX檔案(一個類似APK的安裝檔案)。

最顯著的就是藍芽模組: http://cs.android.com/android/platform/superproject/+/master:packages/modules/Bluetooth/。

這個模組編譯之後是一個類似APK的APEX檔案,這樣的好處是:

藍芽模組可以自己單獨的通過google playstore更新了!!!!

也就是說,以後安卓的某一個模組出了新的fix,使用者不需要再下載完整龐大的img了,安卓只需要通過playstore下載對應的APEX檔案實現更新!

APEX檔案的具體格式可以看這http://source.android.com/docs/core/ota/apex

可以說,安卓在C/S的架構上的進化從來沒停止過。系統開發者的學習腳步也不能停啊。。。

thread_27203231_20210429130153_s_19450_o_w_465_h_413_59117.webp