Android系統服務(SystemService)簡介

来源:https://www.cnblogs.com/linhaostudy/archive/2020/03/08/12441896.html
-Advertisement-
Play Games

什麼是SystemService 我們在Android開發過程中經常會用到各種各樣的系統管理服務,如進行視窗相關的操作會用到視窗管理服務WindowManager,進行電源相關的操作會用到電源管理服務PowerManager,還有很多其他的系統管理服務,如通知管理服務NotifacationMana ...


什麼是SystemService

我們在Android開發過程中經常會用到各種各樣的系統管理服務,如進行視窗相關的操作會用到視窗管理服務WindowManager,進行電源相關的操作會用到電源管理服務PowerManager,還有很多其他的系統管理服務,如通知管理服務NotifacationManager、振動管理服務Vibrator、電池管理服務BatteryManager…… 這些Manager提供了很多對系統層的控制介面。對於App開發者,只需要瞭解這些介面的使用方式就可以方便的進行系統控制,獲得系統各個服務的信息,而不需要瞭解這些介面的具體實現方式。而對於Framework開發者,則需要瞭解這些Manager服務的常用實現模式,維護這些Manager的介面,擴展這些介面,或者實現新的Manager。

image

一個簡單的SystemService

我們從一個簡單的系統服務Vibrator服務來看一下一個系統服務是怎樣建立的。

Vibrator服務提供的控制手機振動的介面,應用可以調用Vibrator的介面來讓手機產生振動,達到提醒用戶的目的。

從Android的官方文檔中可以看到Vibrator只是一個抽象類,只有4個抽象介面:

  • bstract void cancel() 取消振動
  • abstract boolean hasVibrator() 是否有振動功能
  • abstract void vibrate(long[] pattern, int repeat) 按節奏重覆振動
  • abstract void vibrate(long milliseconds) 持續振動

應用中使用振動服務的方法也很簡單,如讓手機持續振動500毫秒:

Vibrator mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrator.vibrate(500);

Vibrator使用起來很簡單,我們再來看一下實現起來是不是也簡單。

從文檔中可以看到Vibrator只是定義在android.os 包里的一個抽象類,在源碼里的位置即frameworks/base/core/java/android/os/Vibrator.java,那麼應用中實際使用的是哪個實例呢?應用中使用的Vibrator實例是通過Context的一個方法getSystemService(Context.VIBRATOR_SERVICE)獲得的,而Context的實現一般都在ContextImpl中,那我們就看一下ContextImpl是怎麼實現getSystemService的:

frameworks/base/core/java/android/app/ContextImpl.java

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

frameworks/base/core/java/android/app/SystemServiceRegistry.java
(SystemServiceRegistry是 Android 6.0之後才有的,Android 6.0 之前的代碼沒有該類,下麵的代碼是直接寫在ContextImpl里的)

 public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

SYSTEM_SERVICE_MAP是一個HashMap,通過我們服務的名字name字元串,從這個HashMap里取出一個ServiceFetcher,再return這個ServiceFetcher的getService()。ServiceFetcher是什麼?它的getService()又是什麼?既然他是從SYSTEM_SERVICE_MAP這個HashMap里get出來的,那就找一找這個HashMap都put了什麼。

通過搜索SystemServiceRegistry可以找到如下代碼:

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

這裡往SYSTEM_SERVICE_MAP里put了一對String與ServiceFetcher組成的key/value對,registerService()又是從哪裡調用的?繼續搜索可以發現很多類似下麵的代碼:

public class SystemVibrator extends Vibrator {
    ...
}

我們再從SystemVibrator看一下系統的振動控制是怎麼實現的。以hasVibrator()為例,這個是查詢當前系統是否能夠振動,在SystemVibrator中它的實現如下:

public boolean hasVibrator() {
    ...
    try {
        return mService.hasVibrator();
    } catch (RemoteException e) {
    }
    ...
}

這裡直接調用了一個mService.hasVibrator()。mService是什麼?哪來的?搜索一下可以發現:

private final IVibratorService mService;
public SystemVibrator() {
    ...
    mService = IVibratorService.Stub.asInterface(
            ServiceManager.getService("vibrator"));
}

mService 是一個IVibratorService,我們先不去管IVibratorService.Stub.asInterface是怎麼回事,先看一下IVibratorService是什麼。搜索一下代碼發現這並不是一個java文件,而是一個aidl文件:

frameworks/base/core/java/android/os/IVibratorService.aidl

AIDL (Android Interface Definition Language) 是Android中的介面定義文件,為系統提供了一種簡單跨進程通信方法。

IVibratorService 中定義了幾個介面,SystemVibrator中使用的也是這幾個介面,包括我們剛纔使用的hasVibrator()

interface IVibratorService
{
    boolean hasVibrator();
    void vibrate(...);
    void vibratePattern(...);
    void cancelVibrate(IBinder token);
}

這裡又只是介面定義,介面實現在哪呢?通過在frameworks/base目錄下進行grep搜索,或者在AndroidXRef搜索,可以發現IVibratorService介面的實現在frameworks/base/services/java/com/android/server/VibratorService.java

public class VibratorService extends IVibratorService.Stub

可以看到 VibratorService實現了IVibratorService定義的所有介面,並通過JNI調用到native層,進行更底層的實現。更底層的實現不是這篇文檔討論的內容,我們需要分析的是VibratorService怎麼成為系統服務的。那麼VibratorService是怎麼註冊為系統服務的呢?在SystemServer裡面:

VibratorService vibrator = null;
...
//實例化VibratorService並添加到ServiceManager
traceBeginAndSlog("StartVibratorService");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
...
//通知服務系統啟動完成
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeVibratorServiceReady");
try {
    vibrator.systemReady();
} catch (Throwable e) {
    reportWtf("making Vibrator Service ready", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

這樣在SystemVibrator里就可以通過下麵的代碼連接到VibratorService,與底層的系統服務進行通信了:

IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));

mService相當於IVibratorService在應用層的一個代理,所有的實現還是在SystemServer的VibratorService里。

看代碼時可以發現registerService是在static代碼塊里靜態調用的,所以getSystemServcr獲得的各個Manager也都是單例的。

System Service實現流程

從上面的分析,我們可以總結出Vibrator服務的整個實現流程:

  1. 定義一個抽象類Vibrator,定義了應用中可以訪問的一些抽象方法
frameworks/base/core/java/android/os/Vibrator.java
  1. 定義具體的類SystemVibrator繼承Vibrator,實現抽象方法

frameworks/base/core/java/android/os/SystemVibrator.java

  1. 定義一個AIDL介面文件IVibratorService,定義系統服務介面

frameworks/base/core/java/android/os/IVibratorService.aidl

  1. 定義服務VibratorService,實現IVibratorService定義的介面

frameworks/base/services/java/com/android/server/VibratorService.java

  1. 將VibratorServicey添加到系統服務

frameworks/base/services/java/com/android/server/SystemServer.java

VibratorService vibrator = null;
...
//實例化VibratorService並添加到ServiceManager
Slog.i(TAG, "Vibrator Service");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
...
//通知服務系統啟動完成
try {
    vibrator.systemReady();
} catch (Throwable e) {
    reportWtf("making Vibrator Service ready", e);
}
  1. 在SystemVibrator中通過IVibratorService的代理連接到VibratorService,這樣SystemVibrator的介面實現里就可以調用IVibratorService的介面:

frameworks/base/core/java/android/os/SystemVibrator.java

private final IVibratorService mService;
...
public SystemVibrator() {
    ...
    mService = IVibratorService.Stub.asInterface(
            ServiceManager.getService("vibrator"));
    ...
    public boolean hasVibrator() {
        ...
        try {
            return mService.hasVibrator();
        } catch (RemoteException e) {
        }
        ...
    }
}
  1. 在Context里定義一個代表Vibrator服務的字元串

frameworks/base/core/java/android/content/Context.java

public static final String VIBRATOR_SERVICE = "vibrator";
  1. 在ContextImpl里添加SystemVibrator的實例化過程

frameworks/base/core/java/android/app/ContextImpl.java

registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
    return new SystemVibrator(ctx);
}});  
  1. 在應用中使用Vibrator的介面
Vibrator mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrator.vibrate(500);
  1. 為保證編譯正常,還需要將AIDL文件添加到編譯配置里

frameworks/base/Android.mk

LOCAL_SRC_FILES += \
...
core/java/android/os/IVibratorService.aidl \

System Service 新加介面

如果我們需要實現一個新的系統服務,就可以按照上面的步驟在系統中擴展出一個新的服務,並給應用層提供出使用介面。如果想在Vibrator里添加一個新的介面,需要下麵3步:

  1. 在IVibratorService添加介面;
  2. 在VibratorService添加介面的實現;
  3. 在Vibrator及SystemVibrator里擴展新的介面;

這樣應用中就可以使用Vibrator的新介面了。

應用層與 System Service 通信

上面的實現我們看到的只是從應用層通過服務代理,調用系統服務的介面,如果我們想反過來,將系統服務的狀態通知給應用層,該怎麼做呢?

  • 方法一:使用Broadcast

我們知道使用Broadcast廣播可以實現跨進程的消息傳遞,一些系統服務也使用了這種方法。如電池管理服務BatteryManagerService,收到底層上報的電池狀態變化信息時,就將當前的電池狀態封裝在一個Intent里,action為android.intent.action.BATTERY_CHANGED。應用只要註冊一個對應的BroadcastReceiver就可以收到BatterManagerService發送的電池狀態信息。

  • 方法二:使用AIDL

從上面我們可以知道,通過AIDL定義一套介面,由系統服務端實現這些介面,應用端使用一個相應的代理就可以訪問系統服務的介面,那反過來讓應用端實現AIDL介面,系統服務端使用代理調用應用端的介面可不可以呢?答案是YES。那麼接下來的問題是怎麼讓系統服務得到這個代理。我們再來看一個LocationManager的例子。

//獲得定位服務
LocationManager locationManager = 
        (LocationManager) getSystemService(Context.LOCATION_SERVICE);

//定義定位監聽器
LocationListener locationListener = new LocationListener() {
    public void onLocationChanged(Location location) {
        //監聽到位置信息
    }
    ...
};

//註冊監聽器
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 
        0, 0, locationListener);

從上面的代碼可以看到,我們創建了一個位置監聽器LocationListener,並將這個監聽器在LocationManager里進行了註冊。當系統定位到系統的位置後,就會回調監聽器的onLocationChanged(),將位置信息通知給監聽器。LocationListener就是一個系統服務調用應用層介面的例子,我們就研究一下LocationListener的實現方式。

我們先從LocationManager怎麼註冊LocationListener開始研究:
frameworks/base/location/java/android/location/LocationManager.java

private final ILocationManager mService;
...
private void requestLocationUpdates(LocationRequest request, 
        LocationListener listener, Looper looper, PendingIntent intent) {
    ...
    // wrap the listener class
    ListenerTransport transport = wrapListener(listener, looper);
    try {
        mService.requestLocationUpdates(request, transport, 
                intent, packageName);
   } catch (RemoteException e) {
       Log.e(TAG, "RemoteException", e);
   }
}

可以看到LocationListener被重新封裝成了一個ListenerTransport,然後傳遞給了ILocationManager ,從前面的分析可以猜測到這個ILocationManager應該就是LocationManagerService的一個代理。那麼ListenerTransport又是什麼呢?搜索LocationManager.java可以找到:

private class ListenerTransport extends ILocationListener.Stub {
    ...
    @Override
    public void onLocationChanged(Location location) {
        ...
    }
}

原來是ILocationListener.Stub的一個繼承實現,那麼ILocationListener應該就是一個AIDL介面定義:
frameworks/base/location/java/android/location/ILocationListener.aidl

oneway interface ILocationListener
{
    void onLocationChanged(in Location location);
    ...
}

而在LocationManagerService里只要調用ILocationListener的方法就可以將消息傳遞給應用層的監聽:

mListener.onLocationChanged(new Location(location));

實現 System Service 的註意事項

  1. 註意防止阻塞
    應用層訪問系統服務提供的介面時會有兩種情況:

一種是應用調用端需要等待服務實現端處理完成,返回處理結果,這樣如果服務端發生阻塞,那麼應用端也會發生阻塞,因此在實現服務端的實現時要註意不要發生阻塞。

另一種是調用端不需要等待服務端返回結果,調用完成後直接返回void,這樣服務端發生阻塞不會影響到應用端,這樣的單向的介面在AIDL里定義時需要添加oneway關鍵字,如:

oneway void statusBarVisibilityChanged(int visibility);

對於需要在服務端調用,在應用端實現的介面,考慮到系統的穩定性以及安全性,一般都會設計成上面的第二種,即AIDL里所有的介面都是單向的,如上面的ILocationListener

oneway interface ILocationListener
  1. 註意多線程訪問

每個系統服務在系統進程中只有一個實例,而且應用中系統服務的代理也是單例的,而且應用端的訪問,在系統進程都是使用獨立的線程進行響應,所以訪問同一個系統服務的介面時必然會出現多個線程或者多個進程同時訪問的情況。為保證系統服務的線程安全,需要對系統服務的進程進行多線程訪問的保護,目前主要有兩種實現線程安全的方法:

一種是通過同步鎖機制,鎖住一個對象實例(一般是這個服務對象本身),這樣這個服務同一時間只能響應一個訪問請求,如LocationManagerService里:

public boolean callStatusChangedLocked(...) {
    ...
    synchronized (this) {
    ...
    }
}

另一種方法就是使用Handler機制,這種服務一般會創建一個單獨的線程,當有應用端訪問請求到來時會向服務線程的Handler里發送一個Message,利用單線程順序執行的特性,保證所有的訪問都按順序進行處理,但這種方法只適合單向的訪問,不適合需要返回的雙向訪問。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.事物隔離級別 讀未提交(read uncommitted)是指,一個事務還沒提交時,它做的變更就能被別的事務看到.通俗理解,別人改數據的事務尚未提交,我在我的事務中也能讀到。 讀提交(read committed)是指,一個事務提交之後,它做的變更才會被其他事務看到。通俗理解,別人改數據的事務已 ...
  • 一.redo log 使用原因 原理 二.binlog(歸檔日誌) 與redo log的區別 三.兩段提交 更新過程 update T set c=c+1 where ID=2; 1. 執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本 ...
  • 查詢 備份查詢數據 插入 插入查詢結果 更新 通過查詢結果更新 刪除 截斷表 刪除和截斷的區別 TRUNCATE 是 DDL 命令,命令執行完就提交,刪除的數據不能恢復; DELETE 命令是 DML 命令,命令執行完需提交後才生效,刪除後的數據可以通過日誌文件恢復。 TRUNCATE 的執行速度比 ...
  • SQL語句介紹 數據定義語言(DDL),包括 CREATE、 ALTER、 DROP等。 數據操縱語言(DML),包括 INSERT、 UPDATE、 DELETE、 SELECT … FOR UPDATE等。 數據查詢語言(DQL),包括基本查詢語句、 Order By 子句、 Group By ...
  • union 結果集合併 使用多個select分別查詢不同的表,把多個select查到的記錄合併在一起 一個select查到m條記錄,另一個select查到n條記錄,合併之後就是m+n條記錄 #查詢全校師生的id、name,使用2個select分別從tb_teacher、tb_student中查,然後 ...
  • 我前幾天刷機未退出Google帳號,導致刷入的新系統驗證一直不過,於是死活登陸不了,其實就是被鎖了。我的解決方案分成三步:1. 刷入第三方不帶google服務的ROM,我使用的是 LineageOS 2. 在第三方的ROM中重新安裝Google服務及市場並登陸Google帳號,然後再次退出3. 重新... ...
  • 今天。。。是一個非常重要的日子 女神節。作為一名程式員,如何向心儀的人低調而又不失逼格的表達祝福,關係到我們後半生的幸福,祝福的到位,普通朋友加個字變成女朋友,女朋友變成老婆,如果已經是老婆了,那麼這個月的零花錢又能多好幾百,想想都激動。 回到現實,作為程式員,我們當然要獨一無二,要與眾不同,要突破 ...
  • 1. 匿名內部類作為事件監聽器 2. 內部類作為事件監聽器 3. Activity本身作為事件監聽器 4. 外部類作為事件監聽器 5. 將事件處理方法直接綁定到標簽 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...