安卓 RemoteCallbackList 的使用 (系統服務篇)


接着上回的繼續說,這次我們在系統服務里面使用 RemoteCallbackList 。

首先,你需要有一份能完整編譯的安卓源碼。

我這里以 Android10_r47為例。

我在 frameworks/base/core/java/com/callback/ 內幾個文件。

  1. 服務接口的 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);
}
  1. 服務回調的 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之后,普通應用就可以像調用安卓已有的服務那樣,來使用新增的這個服務了。

復習一下

有幾個小地方需要注意:

  1. 如果你添加的接口沒有出現在 current.txt 里面的話,安卓10上由於谷歌新增的hidden api訪問限制,普通應用是無法訪問的
  2. 使用AOSP 編譯出來的 signapk.jar 和 platform 簽名的apk,似乎無法在虛擬機上安裝為系統應用,我是把 platform key 轉換成 Android Studio 使用的 keystore 來使用的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM