1. 程式人生 > 程式設計 >Android 新增系統服務的方法詳解

Android 新增系統服務的方法詳解

一、前言

系統服務是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();
    }
  }
}

服務端繼承自 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);
    }});
}

註冊後,如果你在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呼叫的完整流程,如有疑問,歡迎討論!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。