安卓 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 来使用的。
posted @ 2021-05-09 17:15  SupperMary  阅读(2941)  评论(0编辑  收藏  举报