在Android12中添加自定义系统服务——详细步骤和解读

一、简介

系统服务(System Service)是Android操作系统中的一些后台服务组件,它们通过Binder机制提供对系统资源和功能的访问。这些服务运行在系统进程中,通常在设备启动时启动,并在设备运行期间保持活跃。

常见的系统服务有ActivityManager、PackageManager、WindowManager等等。

在Android系统级开发的过程中,有时我们会需要自定义系统服务以供上层APP访问。本文将介绍如何在Android12环境下创建一个自定义系统服务并对个步骤进行解读。

二、正文

在Android中,系统服务可以分为以独立进程形式存在,以及依赖于SystemServer存在两种形式,我们这里选择的是后者。接下来,我们将会一步步创建一个测试用的系统服务。

1. 创建AIDL接口文件

AIDL(Android Interface Definition Language)是用来实现IPC(进程间通信)的一种工具,即实现Binder通信的常用工具。编写以.aidl为后缀的文件,从而可以定义系统服务的接口。

我们可以在framework/base/core/java/android/app/路径下新建myservice文件夹,用以存放aidl文件。

然后,在文件夹中新建一个IMyService.aidl文件:

package android.os.myservice;

import android.os.myservice.IMyCallback;

/*
* @hide
*/
interface IMyService {
    void registerCallback(IMyCallback callback);
    void unregisterCallback(IMyCallback callback);
    void sendMessage(int type, String message);
}
 

可以看到,我们使用了一个Callback接口,所以还需要新建一个IMyCallback.aidl文件:

package android.os.myservice;

/*
* @hide
*/
interface IMyCallback {
    oneway void onMessageReceived(int type, String message);
}
 

显然,AIDL的语法和Java还是非常类似的,很容易理解和编写。

其中IMyCallback.aidl里面的oneway关键字代表这个回调方法是异步执行的,不会阻塞系统服务线程。你也可以把这个关键字加到IMyService的方法前面。这样,调用这个接口方法的时候就不会阻塞,而是立即返回了。当然了,返回值类型就必须是void了。

注意,在AIDL的注释中,我们添加了@hide,@hide意如其名,就是把所修饰的对象在SDK中隐藏起来,避免被APP调用。有人可能会问了,我们的目标就是创建接口给APP调用,这里隐藏起来还怎么使用呢?

其实,AIDL只允许系统层组件之间使用,或者各APP之间使用,是禁止APP直接使用系统组件的AIDL的(参见Android source文档)。

如果你不在AIDL中添加@hide,那么在编译时,会遇到很多安全检查的报错。虽然网上有规避这些错误的取巧方法,但我们这里还是遵循官方文档的要求,不会这么做;取而代之的,这里会封装manager类,文章后半部分会介绍具体办法。

2. 添加服务名称

系统服务都需要一个名称,为了后续代码的编写方便,我们可以把名称定义为一个常量。

需要在frameworks/base/core/java/android/content/Context.java中添加服务名称常量:

diff --git a/frameworks/base/core/java/android/content/Context.java b/frameworks/base/core/java/android/content/Context.java
index 913c3b8a33..e9cf7d6998 100644
--- a/frameworks/base/core/java/android/content/Context.java
+++ b/frameworks/base/core/java/android/content/Context.java
@@ -3728,6 +3728,7 @@ public abstract class Context {
             //@hide: SPEECH_RECOGNITION_SERVICE,
             UWB_SERVICE,
             MEDIA_METRICS_SERVICE,
+            MY_TEST_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -5697,6 +5698,14 @@ public abstract class Context {
      */
     public static final String DISPLAY_HASH_SERVICE = "display_hash";

+    /**
+     * Use with {@link #getSystemService(String)} to access
+     * My testing service.
+     *
+     * @see #getSystemService(String)
+     */
+     public static final String MY_TEST_SERVICE = "my_test";
+
     /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.

我们的服务名称为“my_test”,常量名称为MY_TEST_SERVICE

3. 实现服务内容

定义好接口和名称之后,我们就开始实现服务的具体业务吧。

我们在frameworks/base/services/core/java/com/android/server/路径下创建myservice文件夹,用以存放具体业务代码文件。

首先,我们编写一个类来实现我们上面定义的接口,创建MyServiceImpl.java:

package com.android.server.myservice;

import android.content.Context;
import android.os.myservice.IMyService;
import android.os.myservice.IMyCallback;
import android.os.RemoteException;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;

public final class MyServiceImpl extends IMyService.Stub {
    private static final String TAG = "MyService";

    private List<IMyCallback> mCallbacks = new ArrayList<IMyCallback>();
    private String mValue;

    public void onStart() {
        Slog.i(TAG, "MyService started");
        mValue = "Hello, world!";
    }

    @Override
    public void registerCallback(IMyCallback callback) throws RemoteException {
        if (callback != null) {
            mCallbacks.add(callback);
        }
    }

    @Override
    public void unregisterCallback(IMyCallback callback) throws RemoteException {
        if (callback != null) {
            mCallbacks.remove(callback);
        }
    }

    @Override
    public void sendMessage(int type, String message) throws RemoteException {
        for (IMyCallback callback : mCallbacks) {
            try {
                callback.onMessageReceived(type, message + " " + mValue);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to send message", e);
            }
        }
    }
}

这个IMyService.Stub就是我们上面的AIDL文件所生成的Java类了。你可能会疑惑为什么我们类的名称是Impl,之所以这么命名接下来就会介绍。

4. 启动系统服务

实现好系统服务之后,我们就需要选择合适的时机将其启动了。

关于这个启动或添加的方式有两种(网上更多的叫“两种注册方式”):

  1)通过ServiceManager.addService()方法来将服务直接添加到ServiceManager中去;

  2)通过SystemServiceManager.startService()方法来启动

我们这里选择的是后者。相对于前者呢,主要是可以有一个onStart()方法可以在构造函数外对服务进行初始化,以及有一个onBootPhase()方法可以在不同启动阶段被调用,从而能根据不同的启动阶段来执行不同的任务。

(第二种方式的具体实现里,其实也是调用了ServiceManager.addService(),有兴趣的可以查阅源码或其他资料)

总的来说第二种启动方式相对于第一种方式功能更丰富,我们为了学习,便采取第二种方式。

采取第二种方式的话呢,我们需要在上一步新建的文件夹中再创建一个类,这个类继承自SystemService,重写它的onStart()和 onBootPhase()方法。

首先在frameworks/base/services/core/java/com/android/server/myservice下新建MyService.java(这就是MyServiceImpl命名为Impl的原因):

package com.android.server.myservice;

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

import com.android.server.SystemService;

public class MyService extends SystemService {
    private static final String TAG = "MyService";
    
    private MyServiceImpl mImpl;

    public MyService(Context context) {
        super(context);
        mImpl = new MyServiceImpl();
    }

    @Override
    public void onStart() {
        Slog.i(TAG, "MyService started");
        publishBinderService(Context.MY_TEST_SERVICE, mImpl);
        mImpl.onStart();
    }

    @Override
    public void onBootPhase(int phase) {
        Slog.i(TAG, "MyService onBootPhase: " + phase);
    }
}

注意onStart()中的publishBinderService(Context.MY_TEST_SERVICE, mImpl);方法是必须的,它是SystemService的一个方法,正是在这里面调用了ServiceManager.addService()

有了这个类之后呢,我们就可以添加启动系统服务的代码了。

具体需要在frameworks/base/services/java/com/android/server/SystemServer.java中添加代码

SystemServer.java里面有好几个startXXXServices()方法,应该是在不同的时间以及不同的条件下执行的。我们低调一点,把我们的服务启动代码添加到startOtherServices()里面:

diff --git a/frameworks/base/services/java/com/android/server/SystemServer.java b/frameworks/base/services/java/com/android/server/SystemServer.java
index 86e6561d4c..d2fc8703c8 100644
--- a/frameworks/base/services/java/com/android/server/SystemServer.java
+++ b/frameworks/base/services/java/com/android/server/SystemServer.java
@@ -388,6 +388,7 @@ public final class SystemServer implements Dumpable {
     private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";

     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
+    private static final String MYSERVICE_CLASS = "com.android.server.myservice.MyService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";

@@ -2952,6 +2953,11 @@ public final class SystemServer implements Dumpable {
             }
         }, t);

+        // My Service for test
+        t.traceBegin("StartMyService");
+        mSystemServiceManager.startService(MYSERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("StartSystemUI");
         try {
             startSystemUi(context, windowManagerF);

直接把MyService的路径传参进去就行了。

5.添加selinux权限

Android中所有的进程对于系统资源的访问都受到SELinux策略的约束。由于它实行的是最小权限原则,我们添加自定义服务之后,也要为其添加相应的权限,才能使得APP可以正常访问。

首先在system/sepolicy/private/service_contexts中添加定义:

diff --git a/system/sepolicy/private/service_contexts b/system/sepolicy/private/service_contexts
index 3fd342b9be..7b2524a9bd 100644
--- a/system/sepolicy/private/service_contexts
+++ b/system/sepolicy/private/service_contexts
@@ -183,6 +183,7 @@ memtrack.proxy                            u:object_r:memtrackproxy_service:s0
 midi                                      u:object_r:midi_service:s0
 mount                                     u:object_r:mount_service:s0
 music_recognition                         u:object_r:music_recognition_service:s0
+my_test                                   u:object_r:mytest_service:s0
 netd                                      u:object_r:netd_service:s0
 netpolicy                                 u:object_r:netpolicy_service:s0
 netstats                                  u:object_r:netstats_service:s0

然后,在 system/sepolicy/public/service.te 中添加服务的属性

diff --git a/system/sepolicy/public/service.te b/system/sepolicy/public/service.te
index ba7837d562..c4578fcba6 100644
--- a/system/sepolicy/public/service.te
+++ b/system/sepolicy/public/service.te
@@ -151,6 +151,7 @@ type memtrackproxy_service, app_api_service, ephemeral_app_api_service, system_s
 type midi_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
 type mount_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
 type music_recognition_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
+type mytest_service, app_api_service, system_server_service, service_manager_type;
 type netpolicy_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
 type netstats_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
 type network_management_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;

然后再把这两部分内容也同样添加到system/sepolicy/prebuilts/api下的各个API版本对应的文件中就行了,比如system/sepolicy/prebuilts/api/32.0/private/service_contexts和system/sepolicy/prebuilts/api/32.0/public/service.te

selinux的相关内容还有很多,这里找一个其他的系统服务,照葫芦画瓢添加就行了。

如果想要直接关闭selinux强制检查,可以使用命令 adb shell setenforce 0。当然关闭后,它在运行时还会报出警告,可以按照日志提示来调试添加相应权限。

至此,我们的自定义系统服务其实就已经创建完成了。

如果你想使用的话,可以调用 IBinder binder = ServiceManager.getService(Context.MY_TEST_SERVICE); 来获binder对象,然后通过IMyService.Stub.asInterface(binder);来把它转换为IMyService类型,就可以使用了。

当然了,正如前文所说,这种方法只能在系统组件中使用,是不能通过APP调用的。我们现在要做的,就是再封装一个manager类,来提供接口给APP调用。

6.添加manager类并注册

 上文提到了在APP中,我们不能直接使用AIDL定义的接口。为了解决这个问题,我们可以创建manager类,来对AIDL定义的接口进行封装,这样,APP就可以间接调用了。

首先,我们创建一个MyServiceManager.java,就放在frameworks/base/core/java/android/os/myservice/ 中:

package android.os.myservice;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

@SystemService(Context.MY_TEST_SERVICE)
public class MyServiceManager {
    private static final String TAG = "MyServiceManager";

    private IMyService mService;
    private Executor mExecutor;
    private Map<MyServiceCallback, IMyCallback> mCallbacks = new ConcurrentHashMap<>();

    /**
     * @hide
     */
    public MyServiceManager() {
        IBinder binder = ServiceManager.getService(Context.MY_TEST_SERVICE);
        if (binder != null) {
            mService = IMyService.Stub.asInterface(binder);
        } else {
            Log.e(TAG, "Failed to get service binder");
            throw new IllegalStateException("Failed to get service binder");
        }
    }

    public void registerMyCallback(@Nullable Executor executor, @NonNull MyServiceCallback callback) throws RuntimeException {
        if (mCallbacks.containsKey(callback)) {
            Log.w(TAG, "Callback is already registered");
            return;
        }

        mExecutor = executor;

        IMyCallback iMyCallback = new IMyCallback.Stub() {
            @Override
            public void onMessageReceived(int type, String message) {
                callback.onMessageReceived(type, message);
            }
        };
        mCallbacks.put(callback, iMyCallback);

        try {
            mService.registerCallback(iMyCallback);
        } catch (RemoteException e) {
            mCallbacks.remove(callback);
            Log.e(TAG, "Failed to register callback", e);
            throw new RuntimeException(e);
        }
    }

    public void unregisterMyCallback(@NonNull MyServiceCallback callback) throws RuntimeException {
        IMyCallback iMyCallback = mCallbacks.remove(callback);
        if (iMyCallback != null) {
            try {
                mService.unregisterCallback(iMyCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to unregister callback", e);
                mCallbacks.put(callback, iMyCallback);
                throw new RuntimeException(e);
            }
        } else {
            Log.w(TAG, "Callback not registered");
        }
    }

    public void sendMessage(int type, @Nullable String message) throws RuntimeException {
        try {
            mService.sendMessage(type, message);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to send message", e);
            throw new RuntimeException(e);
        }
    }
}
 

类定义上面的@SystemService(Context.MY_TEST_SERVICE)表明了这是系统服务的入口。

我们在构造函数中完成了对自定义系统服务的获取,并且把每个系统服务的接口都封装了一个可以暴露给APP的方法。

然后注意到了吗,为了避免IMyCallback被暴露出来,我们也没有直接使用IMyCallback作为传参,而是创建了一个MyServiceCallback来和它捆绑到一起。

MyServiceCallback.java的内容如下:
package android.os.myservice;

import android.annotation.Nullable;

public interface MyServiceCallback{
    void onMessageReceived(int type, @Nullable String message);
}
 

以上都使用了@Nullable、@NonNull来修饰部分传参,是为了遵循安全检查,如果你不加,编译会报错。

另外registerMyCallback方法中的Executor参数,也是为了这个目的。我没有使用它是因为我觉得这里用它没有意义。如果你想使用,可以把它的传参和另外一个传参callback绑定在一起,形成一一对应的关系,然后在回调的时候使用相应的Executor来调用callback

最后,我们在frameworks/base/core/java/android/app/SystemServiceRegistry.java中把manager类注册进去就行了:

diff --git a/frameworks/base/core/java/android/app/SystemServiceRegistry.java b/frameworks/base/core/java/android/app/SystemServiceRegistry.java
index 32ea41b2c7..796d464f92 100644
--- a/frameworks/base/core/java/android/app/SystemServiceRegistry.java
+++ b/frameworks/base/core/java/android/app/SystemServiceRegistry.java
@@ -164,6 +164,7 @@ import android.os.ISystemUpdateManager;
 import android.os.IThermalService;
 import android.os.IUserManager;
 import android.os.IncidentManager;
+import android.os.myservice.MyServiceManager;
 import android.os.PerformanceHintManager;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
@@ -1470,6 +1471,13 @@ public final class SystemServiceRegistry {
                         return new DisplayHashManager();
                     }});

+        registerService(Context.MY_TEST_SERVICE, MyServiceManager.class,
+                new CachedServiceFetcher<MyServiceManager>() {
+                    @Override
+                    public MyServiceManager createService(ContextImpl ctx) {
+                        return new MyServiceManager();
+                    }});
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline

至此,我们的代码终于全部完成,可以进行编译了。

首先,由于我们添加了系统API,要先执行make update-api来更新API

然后,在没有任何报错之后,我们就可以执行make进行整编了。

三、验证

Finally,我们可以写APP代码来验证了。

写APP代码之前,我们需要先把系统编译生成的jar包导入到Android Studio中去,这里我直接把framework整个jar包导入了,当然还有更优化的方法,我还没研究。

具体就是把

out\soong\.intermediates\frameworks\base\framework-minus-apex\android_common\combined\framework-minus-apex.jar

这个文件拷贝到Android工程目录libs中去,然后在build.gradle下的dependencies中添加一句

compileOnly files('libs/framework-minus-apex.jar')

就可以了。

这样很简单吧,我们可以import我们的模块了,坏处就是系统中本就存在的模块它还会使用默认的,也就是说我们在Context中添加的服务名称的常量没法方便使用。

看下核心代码片段吧:

import android.os.myservice.MyServiceCallback;
import android.os.myservice.MyServiceManager;

.......

        // 获取服务
        myServiceManager = (MyServiceManager) getContext().getSystemService("my_test");
        // 定义回调
        myServiceManager.registerMyCallback(null, new MyServiceCallback() {
            @Override
            public void onMessageReceived(int i, String s) {
                System.out.println("onMessageReceived: " + s);
            }
        });

.......

        // 点击按钮发送消息
        binding.buttonSndMsg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myServiceManager.sendMessage(666, "Hello, MyServiceManager");
            }
        });

然后我们运行程序,点击按钮,成功收到了回调,并输出日志:

onMessageReceived: Hello, MyServiceManager Hello, world!

大功告成!

四、参考文章

《AIDL Overview》

《Android系统服务的注册方式》

《[Android][踩坑]Android Studio导入framework.jar的各种坑》

posted @ 2024-06-30 21:01  ZySong  阅读(2154)  评论(1)    收藏  举报