Android 添加系統服務的方法

Android 添加系統服務的方法

和你一起終身學習,這裡是程序員 Android

本篇文章主要介紹 Android 開發中的部分知識點,通過閱讀本篇文章,您將收穫以下內容:

一、前言
二、編寫AIDL文件
三、編寫Manager類
四、 編寫系統服務
五、 註冊系統服務
六、註冊Manager
七、App調用
八、添加JNI部分代碼
九、總結

一、前言

系統服務是Android中非常重要的一部分, 像ActivityManagerService, PackageManagerService, WindowManagerService, 這些系統服務都是Framework層的關鍵服務, 本篇文章主要講一下如何基於Android源碼添加一個系統服務的完整流程, 除了添加基本系統服務, 其中還包含添加JNI部分代碼和App通過AIDL調用的演示Demo, 調用包含App調用服務端, 也包含服務端回調App, 也就是完成一個簡單的雙向通信.

注: 測試代碼基於Android 7.1.1, 其他Android版本都是大同小異.

二、編寫AIDL文件

添加服務首先是編寫AIDL文件, AIDL文件路徑如下:

frameworks/base/core/java/com/example/utils/

1.ISystemEvent.aidl 內容如下:

package com.example.utils;

import com.example.utils.IEventCallback;

interface ISystemEvent {
void registerCallback(IEventCallback callback);

void unregisterCallback(IEventCallback callback);

void sendEvent(int type, String value);
}

2.IEventCallback.aidl 內容如下

package com.example.utils;

interface IEventCallback
{
oneway void onSystemEvent(int type, String value);
}

AIDL文件編寫, 教程很多, 我這裡就不詳細說明了, 需要注意的是, 由於我們要實現回調功能, 所以必須寫一個回調接口 IEventCallback, 另外AIDL文件中 oneway 關鍵字表明調用此函數不會阻塞當前線程, 調用端調用此函數會立即返回, 接收端收到函數調用是在Binder線程池中的某個線程中. 可以根據實際項目需求選擇是否需要加 oneway 關鍵字.

AIDL只支持傳輸基本java類型數據, 要想傳遞自定義類, 類需要實現 Parcelable 接口, 另外, 如果傳遞基本類型數組, 需要指定 in out 關鍵字, 比如 void test(in byte[] input, out byte[] output) , 用 in 還是 out, 只需要記住: 數組如果作為參數, 通過調用端傳給被調端, 則使用 in, 如果數組只是用來接受數據, 實際數據是由被調用端來填充的, 則使用 out, 這裡之所以沒有說服務端和客戶端, 是因為 in out 關鍵字用哪個和是服務端還是客戶端沒有聯繫, 遠程調用和被調用更適合描述.

文件寫完後, 添加到編譯的 Android.mk 中 LOCAL_SRC_FILES 後面:

3.frameworks/base/Android.mk

LOCAL_SRC_FILES += \\
core/java/android/view/IWindow.aidl \\
core/java/android/view/IWindowFocusObserver.aidl \\
core/java/android/view/IWindowId.aidl \\
部分代碼省略 ...
core/java/com/example/utils/ISystemEvent.aidl \\
core/java/com/example/utils/IEventCallback.aidl \\
部分代碼省略 ...

編譯代碼, 編譯前需執行 make update-api, 更新接口, 然後編譯代碼,確保AIDL編寫沒有錯誤, 編譯後會生成對應java文件, 服務端要實現對應接口.

三、編寫Manager類

我們可以看到, Android API 中有很多Manager類, 這些類一般都是某個系統服務的客戶端代理類, 其實我們不寫Manager類, 只通過AIDL文件自動生成的類, 也可以完成功能, 但封裝一下AIDL接口使用起來更方便, 我們測試用的Manager類為 SystemEventManager, 代碼如下:
frameworks/base/core/java/com/example/utils/SystemEventManager.java

package com.example.utils;

import android.content.Context;
import android.os.RemoteException;
import android.util.Log;

import com.example.example.ISystemEvent;
import com.example.IEventCallback;

public class SystemEventManager {

private static final String TAG = SystemEventManager.class.getSimpleName();
// 系統服務註冊時使用的名字, 確保和已有的服務名字不衝突
public static final String SERVICE = "test_systemevent";

private final Context mContext;
private final ISystemEvent mService;

public SystemEventManager(Context context, ISystemEvent service) {
mContext = context;
mService = service;
Log.d(TAG, "SystemEventManager init");
}

public void register(IEventCallback callback) {
try {
mService.registerCallback(callback);
} catch (RemoteException e) {
Log.w(TAG, "remote exception happen");
e.printStackTrace();
}
}

public void unregister(IEventCallback callback) {
try {
mService.unregisterCallback(callback);
} catch (RemoteException e) {
Log.w(TAG, "remote exception happen");
e.printStackTrace();
}
}

/**
* Send event to SystemEventService.
*/
public void sendEvent(int type, String value) {
try {
mService.sendEvent(type, value);
} catch (RemoteException e) {
Log.w(TAG, "remote exception happen");
e.printStackTrace();
}
}
}

代碼很簡單, 就封裝了下AIDL接口, 定義了系統服務註冊時用的名字.

public SystemEventManager(Context context, ISystemEvent service)

構造函數中的 ISystemEvent 參數在後面註冊Manager時候會通過Binder相關接口獲取.

編譯代碼, 確保沒有錯誤, 下面編寫系統服務.

四、 編寫系統服務

路徑以及代碼如下:
frameworks/base/services/core/java/com/android/server/example/SystemEventService.java

package com.android.server.example;

import android.content.Context;
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import com.example.utils.ISystemEvent;
import com.example.utils.IEventCallback;

public class SystemEventService extends ISystemEvent.Stub {

private static final String TAG = SystemEventService.class.getSimpleName();
private RemoteCallbackList<ieventcallback> mCallbackList = new RemoteCallbackList<>();

private Context mContext;

public SystemEventService(Context context) {
mContext = context;
Log.d(TAG, "SystemEventService init");
}

@Override
public void registerCallback(IEventCallback callback) {
boolean result = mCallbackList.register(callback);
Log.d(TAG, "register pid:" + Binder.getCallingPid()

+ " uid:" + Binder.getCallingUid() + " result:" + result);

}

@Override
public void unregisterCallback(IEventCallback callback) {
boolean result = mCallbackList.unregister(callback);
Log.d(TAG, "unregister pid:" + Binder.getCallingPid()
+ " uid:" + Binder.getCallingUid() + " result:" + result);

}

@Override
public void sendEvent(int type, String value) {
sendEventToRemote(type, value + " remote");
}

public void sendEventToRemote(int type, String value) {
int count = mCallbackList.getRegisteredCallbackCount();
Log.d(TAG, "remote callback count:" + count);
if (count > 0) {
final int size = mCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
IEventCallback cb = mCallbackList.getBroadcastItem(i);
try {
if (cb != null) {
cb.onSystemEvent(type, value);
}
} catch (RemoteException e) {
e.printStackTrace();
Log.d(TAG, "remote exception:" + e.getMessage());
}
}
mCallbackList.finishBroadcast();
}
}
}

/<ieventcallback>

服務端繼承自 ISystemEvent.Stub, 實現對應的三個方法即可, 需要注意的是, 由於有回調功能, 所以要把註冊的 IEventCallback 加到鏈表裡面, 這裡使用了 RemoteCallbackList, 之所以不能使用普通的 List 或者 Map, 原因是, 跨進程調用, App調用 registerCallback 和 unregisterCallback 時, 即便每次傳遞的都是同一個 IEventCallback 對象, 但到服務端, 經過跨進程處理後, 就會生成不同的對象, 所以不能通過直接比較是否是同一個對象來判斷是不是同一個客戶端對象, Android中專門用來處理跨進程調用回調的類就是 RemoteCallbackList, RemoteCallbackList 還能自動處理App端異常死亡情況, 這種情況會自動移除已經註冊的回調.

RemoteCallbackList 使用非常簡單, 註冊和移除分別調用 register() 和 unregister() 即可, 遍歷所有Callback 稍微麻煩一點, 代碼參考上面的 sendEventToRemote() 方法.

可以看到, 我們測試用的的系統服務邏輯很簡單, 註冊和移除 Callback 調用 RemoteCallbackList 對應方法即可, sendEvent() 方法在App端調用的基礎上, 在字符串後面加上 " remote" 後回調給App, 每個方法也加了log方便理解流程, 服務端代碼就完成了.

五、 註冊系統服務

代碼寫好後, 要註冊到SystemServer中, 所有系統服務都運行在名為 system_server 的進程中, 我們要把編寫好的服務加進去, SystemServer中有很多服務, 我們把我們的系統服務加到最後面, 對應路徑和代碼如下:
frameworks/base/services/java/com/android/server/SystemServer.java

import com.android.server.example.SystemEventService;
import com.example.utils.SystemEventManager;

/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored
* and organized.
*/
private void startOtherServices() {
// 部分代碼省略...
// start SystemEventService
try {
ServiceManager.addService(SystemEventManager.SERVICE,
new SystemEventService(mSystemContext));
} catch (Throwable e) {
reportWtf("starting SystemEventService", e);
}
// 部分代碼省略...
}

通過 ServiceManager 將服務加到SystemServer中, 名字使用 SystemEventManager.SERVICE, 後面獲取服務會通過名字來獲取. 此時, 如果直接編譯運行, 開機後會出現如下錯誤:

E SystemServer: java.lang.SecurityException

E SELinux : avc: denied { add } for service=test_systemevent pid=1940 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0

這個是沒有Selinux權限, 我們需要加上添加服務的權限, 代碼如下:

首先定義類型, test_systemevent 要和添加服務用的名字保持一致
system/sepolicy/service_contexts

wifiscanner u:object_r:wifiscanner_service:s0
wifi u:object_r:wifi_service:s0
window u:object_r:window_service:s0
# 部分代碼省略...
test_systemevent u:object_r:test_systemevent_service:s0
* u:object_r:default_android_service:s0

system/sepolicy/service.te

# 加入剛剛定義好的 test_systemevent_service 類型, 表明它是系統服務
type test_systemevent_service, system_api_service, system_server_service, service_manager_type;

加入上面代碼後, 編譯刷機開機後, 服務就能正常運行了.

六、註冊Manager

系統服務運行好了, 接下來就是App怎麼獲取的問題了, App獲取系統服務, 我們也用通用接口:


context.getSystemService()
在調用 getSystemService() 之前, 需要先註冊, 代碼如下:

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

import com.example.utils.ISystemEvent;
import com.example.utils.SystemEventManager;

static {
// 部分代碼省略, 參考其他代碼, 註冊Manger
registerService(SystemEventManager.SERVICE, SystemEventManager.class,
new CachedServiceFetcher<systemeventmanager>() {
@Override
public SystemEventManager createService(ContextImpl ctx) {
// 獲取服務
IBinder b = ServiceManager.getService(SystemEventManager.SERVICE);
// 轉為 ISystemEvent
ISystemEvent service = ISystemEvent.Stub.asInterface(b);
return new SystemEventManager(ctx.getOuterContext(), service);
}});
}

/<systemeventmanager>

註冊後, 如果你在App裡面通過 getSystemService(SystemEventManager.SERVICE); 獲取Manager並調用接口, 會發現又會出錯, 又是Selinux權限問題:

E SELinux : avc: denied { find } for service=test_systemevent pid=4123 uid=10035 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:test_systemevent_service:s0 tclass=service_manager permissive=0

說是沒有 find 權限, 因此又要加權限, 修改代碼如下:
system/sepolicy/untrusted_app.te

# 允許 untrusted_app 查找 test_systemevent_service
allow untrusted_app test_systemevent_service:service_manager find;

這個 Selinux 的知識有興趣自己去學一下, 報了什麼權限, 就按照錯誤信息去對應文件添加權限.

至此, 系統代碼修改完成了, 編譯系統刷機, 下面通過App調用.

七、App調用

文件拷貝和準備:
我們需要複製三個文件到App中, 兩個AIDL文件, 一個Manager文件:

IEventCallback.aidl
ISystemEvent.aidl
SystemEventManager.java

所有AIDL文件和java文件, 在App工程中的包名和路徑都需要和系統保持一致, 這三個文件App不能做任何修改, 除非系統源碼中也做對應修改, 總的來說, 這三個文件App和系統中要完全保持一致, 類名包名和包路徑都需一致, 複製這三個文件到工程中後, 編譯後, 調用方式如下.

獲取服務:

SystemEventManager eventManager = (SystemEventManager) 
context.getSystemService(SystemEventManager.SERVICE);

這裡Android Studio可能會報 getSystemService() 參數不是Context裡面的某個服務的錯誤, 可以直接忽略, 不影響編譯.

註冊/取消註冊:

eventManager.register(eventCallback);

eventManager.unregister(eventCallback);


private IEventCallback.Stub eventCallback = new IEventCallback.Stub() {
@Override
public void onSystemEvent(int type, String value) throws RemoteException {
Log.d("SystemEvent", "type:" + type + " value:" + value);
}
};

調用:

eventManager.sendEvent(1, "test string");

測試Log如下:

D SystemEventManager: SystemEventManager init
D SystemEventService: register pid:3944 uid:10035 result:true
D SystemEventService: remote callback count:1
D SystemEvent: type:1 value:test string remote
D SystemEventService: unregister pid:3944 uid:10035 result:true

可以看到調用了服務端併成功收到服務端拼接的字符串.

八、添加JNI部分代碼

我們一般添加系統服務, 可能是為了調用驅動裡面的代碼, 所有一般要用JNI部分代碼, 這裡不是講怎麼編寫JNI代碼, 而是說下系統服務中已有的JNI代碼, 我們可以直接在這基礎上增加我們的功能.

JNI部分代碼位置為:

frameworks/base/services/core/jni/

編譯對應mk為:

frameworks/base/services/Android.mk
frameworks/base/services/core/jni/Android.mk

此部分代碼直接編譯為 libandroid_servers 動態庫, 在SystemServer進行加載:
frameworks/base/services/java/com/android/server/SystemServer.java

// Initialize native services.
System.loadLibrary("android_servers");

如果需要添加JNI部分代碼, 直接在 frameworks/base/services/core/jni/目錄下增加對應文件,
在frameworks/base/services/core/jni/Android.mk中加入新增文件進行編譯即可.
同時按照已有文件中JNI函數註冊方式, 寫好對應註冊方法, 統一在
frameworks/base/services/core/jni/onload.cpp中動態註冊函數.
關於JNI動態註冊知識, 可參考之前寫的一篇文章: 兩種JNI註冊方式

九、總結

從上面一個完整的流程下來, 基本就理解了我們平常調用 getSystemService() 具體是怎麼工作的, 總體來說也不麻煩, 真正有技術含量的跨進程調用被隱藏起來了, 我們只管按照規則調用接口即可,以上就是Android系統中添加一個系統服務和App調用的完整流程, 如有疑問, 歡迎討論!
文章轉載網絡,原文鏈接如下:
原文鏈接


分享到:


相關文章: