接着上回的繼續說,這次我們在系統服務里面使用 RemoteCallbackList 。
首先,你需要有一份能完整編譯的安卓源碼。
我這里以 Android10_r47為例。
我在 frameworks/base/core/java/com/callback/
內幾個文件。
- 服務接口的 aidl
// ICallBackTestInterface.aidl
package com.callback;
// Declare any non-default types here with import statements
import com.callback.ICallbackTestCallback;
interface ICallBackTestInterface {
// 向服務端注冊客戶端回調
void register(ICallbackTestCallback callback);
// 向服務端注銷客戶端回調
void unregister(ICallbackTestCallback callback);
// 向服務端發送消息
void callServer(String msg);
}
- 服務回調的 aidl
// ICallbackTestCallback.aidl
package com.callback;
// Declare any non-default types here with import statements
interface ICallbackTestCallback {
/**
* 服務端調用客戶端的回調
**/
void onReceived(String msg);
}
aidl 的文件會自動編譯成Binder對象的子類,這個編譯系統已經為我們做好了
3. 服務端實現
然后,frameworks/base/services/core/java/com/android/server/CallBackTestService.java
。在這里新建一個文件,用來作為服務端的真正實現
package com.android.server;
import android.util.Slog;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.Binder;
import android.os.IBinder;
import com.callback.ICallbackTestCallback;
import com.callback.ICallBackTestInterface;
public class CallBackTestService extends ICallBackTestInterface.Stub {
private final String TAG = "testcallback";
private boolean serverRunning = false;
private final RemoteCallbackList<ICallbackTestCallback> clients = new RemoteCallbackList<>();
public CallBackTestService(){
serverRunning = true;
new Thread(serverRunnable).start();
}
public void register(ICallbackTestCallback callback) throws RemoteException {
Slog.d(TAG,"register callback from pid=" + Binder.getCallingPid());
clients.register(callback);
}
public void unregister(ICallbackTestCallback callback) throws RemoteException {
Slog.d(TAG,"unregister callback from pid=" + Binder.getCallingPid());
clients.unregister(callback);
}
public void callServer(String msg) throws RemoteException {
Slog.d(TAG,"received pid=" + Binder.getCallingPid()+" message: " + msg);
}
// 向客戶端發送消息的具體實現
// 簡單的做一個自增運算,然后發送回客戶端
//
private Runnable serverRunnable = () ->{
int count = 0;
while(serverRunning){
try {
Thread.sleep(500);
noteClients(Integer.toString(count++));
count = count > 10000 ? 0 : count;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/**
*
* @param msg
*/
private void noteClients(String msg){
int cb = clients.beginBroadcast();
for(int i=0;i<cb;i++){
try {
clients.getBroadcastItem(i).onReceived(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
clients.finishBroadcast();
}
}
這里服務端的實現,實際上就和上一篇中的 CallBackServer 是一個意思。
然后封裝一下服務的調用 frameworks/base/core/java/com/callback/CallBackTestManager.java
package com.callback;
import android.util.Slog;
import android.content.Context;
import android.os.RemoteException;
import android.annotation.UnsupportedAppUsage;
import com.callback.ICallbackTestCallback;
import com.callback.ICallBackTestInterface;
public class CallBackTestManager {
public static final String SERVICE_NAME = "callbacktest";
private final Context mContext;
private final ICallBackTestInterface mService;
public CallBackTestManager(Context context, ICallBackTestInterface service){
mContext = context;
mService = service;
}
public void register(ICallbackTestCallback callback) {
try{
mService.register(callback);
} catch (RemoteException e){
throw e.rethrowFromSystemServer();
}
}
public void unregister(ICallbackTestCallback callback) {
try{
mService.unregister(callback);
} catch (RemoteException e){
throw e.rethrowFromSystemServer();
}
}
public void callserver(String msg) {
try{
mService.callServer(msg);
} catch (RemoteException e){
throw e.rethrowFromSystemServer();
}
}
}
到這里,我們的大部分工作就完成了。不過此時編譯成系統鏡像燒錄或者編譯成虛擬機,還是不能調用這個服務的。
由於我們添加的類位於 frameworks/base/com 這個目錄下,這個目錄下的包並不在 bootclass 里面,修改一下這個文件
diff --git a/build/make/core/tasks/check_boot_jars/package_whitelist.txt b/build/make/core/tasks/check_boot_jars/package_whitelist.txt
index 38f2be57af..883b6d86ff 100644
--- a/build/make/core/tasks/check_boot_jars/package_whitelist.txt
+++ b/build/make/core/tasks/check_boot_jars/package_whitelist.txt
@@ -237,6 +237,7 @@ org\.apache\.xalan\.xslt
# Packages in the google namespace across all bootclasspath jars.
com\.google\.android\..*
com\.google\.vr\.platform.*
+com\.callback\.*
###################################################
# Packages used for Android in Chrome OS
首先會遇到的問題是 selinux 的限制,
按照下面的這個 diff 文件來添加關於這個新增加的服務的規則
diff --git a/system/sepolicy/prebuilts/api/29.0/private/service.te b/system/sepolicy/prebuilts/api/29.0/private/service.te
index a8ee195590..7d9a2452fa 100644
--- a/system/sepolicy/prebuilts/api/29.0/private/service.te
+++ b/system/sepolicy/prebuilts/api/29.0/private/service.te
@@ -5,3 +5,4 @@ type gsi_service, service_manager_type;
type incidentcompanion_service, system_api_service, system_server_service, service_manager_type;
type stats_service, service_manager_type;
type statscompanion_service, system_server_service, service_manager_type;
+type callbacktest_service, system_api_service, system_server_service, service_manager_type;
diff --git a/system/sepolicy/prebuilts/api/29.0/private/service_contexts b/system/sepolicy/prebuilts/api/29.0/private/service_contexts
index 96d553bf49..9067330ae5 100644
--- a/system/sepolicy/prebuilts/api/29.0/private/service_contexts
+++ b/system/sepolicy/prebuilts/api/29.0/private/service_contexts
@@ -220,3 +220,4 @@ wifiaware u:object_r:wifiaware_service:s0
wifirtt u:object_r:rttmanager_service:s0
window u:object_r:window_service:s0
* u:object_r:default_android_service:s0
+callbacktest u:object_r:callbacktest_service:s0
diff --git a/system/sepolicy/prebuilts/api/29.0/private/untrusted_app_all.te b/system/sepolicy/prebuilts/api/29.0/private/untrusted_app_all.te
index 3c20c082b7..d8e2fac17d 100644
--- a/system/sepolicy/prebuilts/api/29.0/private/untrusted_app_all.te
+++ b/system/sepolicy/prebuilts/api/29.0/private/untrusted_app_all.te
@@ -100,6 +100,7 @@ allow untrusted_app_all radio_service:service_manager find;
allow untrusted_app_all app_api_service:service_manager find;
allow untrusted_app_all vr_manager_service:service_manager find;
allow untrusted_app_all gpu_service:service_manager find;
+allow untrusted_app_all callbacktest_service:service_manager find;
# Allow untrusted apps to interact with gpuservice
binder_call(untrusted_app_all, gpuservice)
diff --git a/system/sepolicy/private/service.te b/system/sepolicy/private/service.te
index a8ee195590..7d9a2452fa 100644
--- a/system/sepolicy/private/service.te
+++ b/system/sepolicy/private/service.te
@@ -5,3 +5,4 @@ type gsi_service, service_manager_type;
type incidentcompanion_service, system_api_service, system_server_service, service_manager_type;
type stats_service, service_manager_type;
type statscompanion_service, system_server_service, service_manager_type;
+type callbacktest_service, system_api_service, system_server_service, service_manager_type;
diff --git a/system/sepolicy/private/service_contexts b/system/sepolicy/private/service_contexts
index 96d553bf49..9067330ae5 100644
--- a/system/sepolicy/private/service_contexts
+++ b/system/sepolicy/private/service_contexts
@@ -220,3 +220,4 @@ wifiaware u:object_r:wifiaware_service:s0
wifirtt u:object_r:rttmanager_service:s0
window u:object_r:window_service:s0
* u:object_r:default_android_service:s0
+callbacktest u:object_r:callbacktest_service:s0
diff --git a/system/sepolicy/private/untrusted_app_all.te b/system/sepolicy/private/untrusted_app_all.te
index 3c20c082b7..d8e2fac17d 100644
--- a/system/sepolicy/private/untrusted_app_all.te
+++ b/system/sepolicy/private/untrusted_app_all.te
@@ -100,6 +100,7 @@ allow untrusted_app_all radio_service:service_manager find;
allow untrusted_app_all app_api_service:service_manager find;
allow untrusted_app_all vr_manager_service:service_manager find;
allow untrusted_app_all gpu_service:service_manager find;
+allow untrusted_app_all callbacktest_service:service_manager find;
# Allow untrusted apps to interact with gpuservice
binder_call(untrusted_app_all, gpuservice)
到這里之后,回到Android源碼根目錄,正常make update-api && mae
之后,就可以啟動虛擬機了。
然后再新建一個應用。
.
├── aidl
│ └── com
│ └── callback
│ ├── ICallbackTestCallback.aidl
│ └── ICallBackTestInterface.aidl
├── AndroidManifest.xml
├── java
│ └── com
│ └── callback
│ ├── CallBackTestManager.java
│ └── demo
│ └── MainActivity.java
其中,ICallbackTestCallback.aidl ,ICallBackTestInterface.aidl, CallBackTestManager.java 三個文件都是從我們添加在源碼里面的拷貝出來的,因為Android Studio的sdk里面並沒有這些實現,把這些文件放到工程的目錄里面,讓編譯可以通過。
通過 java 的類加載機制,實際上運行的時候是編譯到系統里啟動的那些代碼,實際上我們拷貝到工程里的文件,只要接口聲明與系統內的一致就可以,接口實現可以留空。就像Android SDK 里面的andrroid.jar里面的那些空接口一樣。
MainActivity.java的內容如下
package com.callback.demo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import com.callback.ICallbackTestCallback;
import com.callback.CallBackTestManager;
import com.testcallback.demo.R;
public class MainActivity extends AppCompatActivity {
private final String TAG = "testcallback";
CallBackTestManager callbackSerice = null;
private boolean bound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
if(bound){
registerCallback(null);
}
}
@Override
protected void onPause() {
super.onPause();
if(bound){
unregisterCallback(null);
}
}
private ICallbackTestCallback callback = new ICallbackTestCallback.Stub(){
@Override
public void onReceived(String msg) throws RemoteException {
Log.d(TAG,"received msg: " + msg + " . from server pid=" + Binder.getCallingPid());
}
};
public void registerCallback(View view) {
if(callbackSerice!=null){
callbackSerice.register(callback);
}
}
public void unregisterCallback(View view) {
if(callbackSerice!=null){
callbackSerice.unregister(callback);
}
}
public void bindService(View view) {
if(callbackSerice == null){
callbackSerice = (CallBackTestManager)getSystemService(CallBackTestManager.SERVICE_NAME);
bound = true;
}
}
public void notifyService(View view) {
if(callbackSerice!=null){
callbackSerice.callserver("hello, I'm client");
}
}
}
對應的資源文件如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/registerBotton"
android:textAllCaps="false"
android:layout_marginLeft="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="registerCallback"
android:text="register"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/unregisterBotton"
android:textAllCaps="false"
android:layout_marginLeft="5dp"
app:layout_constraintTop_toBottomOf="@id/registerBotton"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="unregisterCallback"
android:text="unregister"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bindservice"
android:textAllCaps="false"
android:layout_marginLeft="5dp"
app:layout_constraintTop_toBottomOf="@id/unregisterBotton"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="bindService"
android:text="Bind service"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/notifyservice"
android:textAllCaps="false"
android:layout_marginLeft="5dp"
app:layout_constraintTop_toBottomOf="@id/bindservice"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="notifyService"
android:text="Notify service"/>
編譯完成之后,我們將apk用AOSP的系統簽名(如果你用了自己的密鑰,就是你自己密鑰的platform簽名),AOSP的簽名位於build/target/product/security/
,其內的 platform.pk8 和 platform.x509.pem 。用這兩個簽名給apk簽名安裝。這個apk需要注意生命systemuid。
p.s: 這里使用系統簽名,是因為按照本篇的流程,添加后的 CallBackTestManager 是 hide 的接口,Android 10 對hide接口做了限制,普通app直接去調的話,會報找不到接口。對於安卓10新增的hidden api 調用限制,可以前往https://developer.android.google.cn/guide/app-compatibility/restrictions-non-sdk-interfaces
普通應用直接調用hide的接口,可能會直接閃退,查看log,有類似下面這樣的log生成
Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
把我們新增的接口變成public的接口
這一部分,你可以理解為將我們自行增加的接口變成 SDK 接口(如果你的ROM需要通過谷歌的CTS認證,則不可以將自行增加的API變成SDK API)。
如果你有留意,在 make update-api 之后,android10_r47/frameworks/base/api/current.txt
,會出現新增的一些和aidl文件有關的內容
package com.callback {
public interface ICallBackTestInterface extends android.os.IInterface {
method public void callServer(String) throws android.os.RemoteException;
method public void register(com.callback.ICallbackTestCallback) throws android.os.RemoteException;
method public void unregister(com.callback.ICallbackTestCallback) throws android.os.RemoteException;
}
public static class ICallBackTestInterface.Default implements com.callback.ICallBackTestInterface {
ctor public ICallBackTestInterface.Default();
method public android.os.IBinder asBinder();
method public void callServer(String) throws android.os.RemoteException;
method public void register(com.callback.ICallbackTestCallback) throws android.os.RemoteException;
method public void unregister(com.callback.ICallbackTestCallback) throws android.os.RemoteException;
}
public abstract static class ICallBackTestInterface.Stub extends android.os.Binder implements com.callback.ICallBackTestInterface {
ctor public ICallBackTestInterface.Stub();
method public android.os.IBinder asBinder();
method public static com.callback.ICallBackTestInterface asInterface(android.os.IBinder);
method public static com.callback.ICallBackTestInterface getDefaultImpl();
method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
method public static boolean setDefaultImpl(com.callback.ICallBackTestInterface);
}
public interface ICallbackTestCallback extends android.os.IInterface {
method public void onReceived(String) throws android.os.RemoteException;
}
public static class ICallbackTestCallback.Default implements com.callback.ICallbackTestCallback {
ctor public ICallbackTestCallback.Default();
method public android.os.IBinder asBinder();
method public void onReceived(String) throws android.os.RemoteException;
}
public abstract static class ICallbackTestCallback.Stub extends android.os.Binder implements com.callback.ICallbackTestCallback {
ctor public ICallbackTestCallback.Stub();
method public android.os.IBinder asBinder();
method public static com.callback.ICallbackTestCallback asInterface(android.os.IBinder);
method public static com.callback.ICallbackTestCallback getDefaultImpl();
method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
method public static boolean setDefaultImpl(com.callback.ICallbackTestCallback);
}
}
某種程度上,你可以認為,這個txt的內容就是SDK api,可以看到,我們新增的 CallBackTestManager 的內容並不在這個txt里面。可以通過修改 frameworks/base/Android.bp
這個文件,修改內容如下
diff --git a/frameworks/base/Android.bp b/frameworks/base/Android.bp
index 663354835e..03a98395e5 100644
--- a/frameworks/base/Android.bp
+++ b/frameworks/base/Android.bp
@@ -1237,6 +1237,7 @@ packages_to_document = [
"javax/microedition/khronos",
"org/apache/http/conn",
"org/apache/http/params",
+ "com/callback",
]
// Make the api/current.txt file available for use by modules in other
修改這個文件之后,再次 make update-api
,可以看到 CallBackTestManager 的內容已經添加到 current.txt 里面去了。
再次make之后,普通應用就可以像調用安卓已有的服務那樣,來使用新增的這個服務了。
復習一下
有幾個小地方需要注意:
- 如果你添加的接口沒有出現在 current.txt 里面的話,安卓10上由於谷歌新增的hidden api訪問限制,普通應用是無法訪問的
- 使用AOSP 編譯出來的 signapk.jar 和 platform 簽名的apk,似乎無法在虛擬機上安裝為系統應用,我是把 platform key 轉換成 Android Studio 使用的 keystore 來使用的。