在 Android Framework 中添加自定义系统服务并将其完全暴露给第三方应用,需要打通从 AIDL 接口定义、SystemServer 注册、SELinux 权限配置到 ART 虚拟机 API 白名单扫描的完整闭环。只要遗漏任何一个细节(如常量命名匹配、@hide 注解清理、缓存清除),都会导致调用失败。
以下是基于
aosp_android12_r27(编译目标sdk_car_x86_64-userdebug) 的完整流程
一、 底层接口与服务实现 (AIDL & Service)
- 定义 AIDL 接口
创建文件:frameworks/base/core/java/android/app/IXayeManager.aidl
注意:底层接口本身可以使用 @hide 隐藏,不对外暴露,保证系统安全性。
kotlin
package android.app;
/**
* @hide
*/
interface IXayeManager {
String request(String msg);
}
- 实现服务端逻辑
创建文件:frameworks/base/services/core/java/com/android/server/xaye/XayeManagerService.java
frameworks/base/services/core/java/com/android/server/xaye/ 文件夹为新创建的
继承自 AIDL 生成的 Stub,并实现具体的业务逻辑。
kotlin
package com.android.server.xaye;
import android.app.IXayeManager;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;
public class XayeManagerService extends IXayeManager.Stub {
private static final String TAG = "XayeManagerService";
private final Context mContext;
public XayeManagerService(Context context) {
this.mContext = context;
Slog.i(TAG, "XayeManagerService 实例已创建");
}
@Override
public String request(String msg) throws RemoteException {
Slog.d(TAG, "收到客户端请求数据: " + msg);
return "XayeManagerService 接收数据:" + msg;
}
}
二、 应用层 Manager 封装与 API 暴露
- 注册 Context 常量
修改:frameworks/base/core/java/android/content/Context.java
kotlin
/**
* 自定义 Xaye 服务
*/
public static final String XAYE_SERVICE = "xaye";
- 编写 Manager 代理类
创建文件:frameworks/base/core/java/android/app/XayeManager.java
kotlin
package android.app;
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 android.util.Singleton;
import android.compat.annotation.UnsupportedAppUsage;
@SystemService(Context.XAYE_SERVICE)
public class XayeManager {
private static final String TAG = "XayeManager";
/**
* @hide
*/
public XayeManager() {
}
/**
* 内部获取 Binder 的逻辑可以隐藏
* @hide
*/
@UnsupportedAppUsage
public static IXayeManager getService() {
return IXayeManagerSingleton.get();
}
@UnsupportedAppUsage
private static final Singleton<IXayeManager> IXayeManagerSingleton =
new Singleton<IXayeManager>() {
@Override
protected IXayeManager create() {
final IBinder b = ServiceManager.getService(Context.XAYE_SERVICE);
final IXayeManager im = IXayeManager.Stub.asInterface(b);
if (im == null) {
Log.e(TAG, "无法获取 XayeManagerService,请检查服务是否注册及 SELinux 权限");
}
return im;
}
};
/**
* 对外业务方法必须公开,不能有 @hide
*/
public String request(String msg) {
try {
IXayeManager service = getService();
if (service != null) {
return service.request(msg);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return "Service Unavailable";
}
}
三、 系统服务注册
修改:frameworks/base/services/java/com/android/server/SystemServer.java
在 startOtherServices() 方法中注册你的服务。
避坑:注册时使用的字符串名称,必须与
_Context.XAYE_SERVICE_的值完全一致。
kotlin
import com.android.server.xaye.XayeManagerService;
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// ... 前面有很多其他服务的启动代码 ...
t.traceBegin("StartXayeManagerService");
try {
// 这里的名称必须使用 Context 中定义的常量名
ServiceManager.addService(Context.XAYE_SERVICE, new XayeManagerService(context));
} catch (Throwable e) {
reportWtf("starting XayeManagerService", e);
}
t.traceEnd();
// ...
}
四、 SELinux 权限放行 (核心重点)
SELinux 是导致服务注册失败或找不到的最常见原因。
文件涉及:
system/sepolicy/private/service_contexts
system/sepolicy/private/service.te
system/sepolicy/prebuilts/api/31.0/private/service_contexts
system/sepolicy/prebuilts/api/31.0/private/service.te
system/sepolicy/prebuilts/api/32.0/private/service_contexts
system/sepolicy/prebuilts/api/32.0/private/service.te
- 定义服务类型
在 system/sepolicy/private/service.te 中 添加 (如果是厂商定制,通常在 device/<vendor>/sepolicy 下) :
kotlin
type xaye_service, system_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
- 绑定服务名称与类型
在 system/sepolicy/private/service_contexts 中添加:
避坑:左侧的字符串必须与 Java 代码中的 Context.XAYE_SERVICE 完全一致!
kotlin
accessibility u:object_r:accessibility_service:s0
account u:object_r:account_service:s0
activity u:object_r:activity_service:s0
xaye u:object_r:xaye_service:s0 //添加这一行
其他两个文件和这两个文件的内容要保持完全一致!多一个换行一个空格都不行!!!
- 赋予第三方应用查找权限
在 system/sepolicy/private/untrusted_app_all.te中添加:
kotlin
allow untrusted_app_all app_api_service:service_manager find;
allow untrusted_app_all vr_manager_service:service_manager find;
allow untrusted_app_all xaye_service:service_manager find; // 添加这一行
m 全编报错:
文件 system/sepolicy/prebuilts/api/32.0/private/service.te 和 system/sepolicy/private/service.te 不同
文件 system/sepolicy/prebuilts/api/32.0/private/service_contexts 和 system/sepolicy/private/service_contexts 不同
文件 system/sepolicy/prebuilts/api/32.0/private/untrusted_app_all.te 和 system/sepolicy/private/untrusted_app_all.te 不同
上面我们修改了 31.0 文件夹下的文件,根据提示也要在 32.0 添加,继续 m 后, 继续报错
文件 system/sepolicy/prebuilts/api/32.0/private/service.te 和 system/sepolicy/private/service.te 不同
文件 system/sepolicy/prebuilts/api/32.0/private/untrusted_app_all.te 和 system/sepolicy/private/untrusted_app_all.te 不同
第一个文件是多了一个空格
第二个文件是 32.0/private/untrusted_app_all.te中未添加 allow untrusted_app_all xaye_service:service_manager find;
继续全编,成功!
五、 API 白名单更新与全量编译
- 更新系统 API 白名单
kotlin
# 1. 初始化环境(如果你已经初始化过可跳过)
source build/envsetup.sh
lunch sdk_car_x86_64-userdebug
# 2. 更新 API 签名(处理 Android.bp 和 AIDL 的变动)
make update-api
# 3. 全量编译(执行真正的 AIDL 转 Java 以及镜像打包)
m -j$(nproc)
- 全量编译系统镜像
kotlin
m -j$(nproc)
- 强制清空缓存并冷启动模拟器
kotlin
emulator -writable-system -wipe-data
正确的输出日志:
kotlin
xaye@orange:~$ adb logcat | grep -i Xaye
- waiting for device -
04-25 23:53:16.538 0 0 I init : Setting property 'ro.build.fingerprint' to 'Android/sdk_car_x86_64/emulator_car_x86_64:12/SP2A.220505.008/eng.xaye.20260410.173847:userdebug/test-keys'
04-25 23:53:24.714 371 371 I zygote64: option[48]=-Xfingerprint:Android/sdk_car_x86_64/emulator_car_x86_64:12/SP2A.220505.008/eng.xaye.20260410.173847:userdebug/test-keys
04-25 23:53:27.325 375 375 I zygote : option[47]=-Xfingerprint:Android/sdk_car_x86_64/emulator_car_x86_64:12/SP2A.220505.008/eng.xaye.20260410.173847:userdebug/test-keys
04-25 23:53:41.167 704 704 I SystemServerTiming: StartXayeervice
04-25 23:53:41.167 704 704 I XayeManagerService: XayeManagerService 实例已创建
04-25 23:53:41.168 704 704 D SystemServerTiming: StartXayeervice took to complete: 1ms
adb 查看服务:

系统中我们的服务启动起来了。
顺便把我的编译脚本和模拟器启动脚本贴出来:
car_make_all.sh
kotlin
#!/bin/bash
rm -rf /home/xaye/code/aosp_android12_r27/out/target/product/generic_car_x86_64/*.img
# 1. 环境初始化
source build/envsetup.sh
lunch sdk_car_x86_64-userdebug
# 2. 编译 (如果已经编译过且没改代码,这一步会很快)
make -j$(nproc)
emulator_car.sh
kotlin
#!/bin/bash
export ANDROID_PRODUCT_OUT=/home/xaye/code/aosp_android12_r27/out/target/product/emulator_car_x86_64
source build/envsetup.sh;
lunch sdk_car_x86_64-userdebug;
emulator -writable-system -wipe-data;
注意:只要修改 Java 代码就需要 make update-api 和 m 全编,总耗时在一个小时左右。
六、 第三方应用调用
因为默认的安卓 SDK 不包含我们自定义的服务和常量信息,尽管我们的系统已经编译成功了,但是 APP 编译的环境还是官方默认的,所以需要在项目中导入我们编译后的 class.jar 包,让 APP 可以编译通过。
- 引入定制的 Framework jar
从 AOSP 编译产出目录(out/target/common/obj/JAVA_LIBRARIES/framework-minus-apex_intermediates/classes.jar)提取 jar 包,放入 App 的 app/libs 目录下。
修改 App 的 build.gradle:
kotlin
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs.add("-Xbootclasspath/p:${rootDir}/app/libs/classes.jar")
}
}
dependencies {
// 必须是 compileOnly,防止类冲突
compileOnly files('libs/classes.jar')
}
注意:Java 版本必须是 VERSION_1_8,否则仍编译不过。
- 业务代码调用
kotlin
// 就像使用原生的 ActivityManager 一样
XayeManager manager = (XayeManager) getSystemService(Context.XAYE_SERVICE);
if (manager != null) {
String result = manager.request("Hello from App!");
Log.d("XayeApp", "收到系统底层回复: " + result);
}
但是,到这里你可能会遇到个问题,APP 在运行到 manager.request 这行代码时报错:
java.lang.NoSuchMethodError: No virtual method request(Ljava/lang/String;)Ljava/lang/String; in class Landroid/app/XayeManager; or its super classes (declaration of 'android.app.XayeManager' appears in /system/framework/framework.jar)
意思就是服务存在,但这个方法匹配不到,捣鼓了一阵子也没有眉头,如果想临时看结果的话,输入下面命令就行了:
将隐藏 API 限制策略设置为 1(完全放行) adb shell settings put global hidden_api_policy 1
但并不是长久之计,目前我是没找到问题出在哪里,如果你知道的话,还望不吝赐教:)