一、简介
系统服务(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
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
/**
* @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
+ @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的各种坑》