Android 12 (AOSP) 添加自定义系统服务

在 Android Framework 中添加自定义系统服务并将其完全暴露给第三方应用,需要打通从 AIDL 接口定义、SystemServer 注册、SELinux 权限配置到 ART 虚拟机 API 白名单扫描的完整闭环。只要遗漏任何一个细节(如常量命名匹配、@hide 注解清理、缓存清除),都会导致调用失败。

以下是基于 aosp_android12_r27 (编译目标 sdk_car_x86_64-userdebug) 的完整流程

一、 底层接口与服务实现 (AIDL & Service)

  1. 定义 AIDL 接口

创建文件:frameworks/base/core/java/android/app/IXayeManager.aidl

注意:底层接口本身可以使用 @hide 隐藏,不对外暴露,保证系统安全性。

kotlin 复制代码
package android.app;

/**
 * @hide
 */
interface IXayeManager {
    String request(String msg);
}
  1. 实现服务端逻辑

创建文件: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 暴露

  1. 注册 Context 常量

修改:frameworks/base/core/java/android/content/Context.java

kotlin 复制代码
/**
 * 自定义 Xaye 服务
 */
 public static final String XAYE_SERVICE = "xaye";
  1. 编写 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

  1. 定义服务类型

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;
  1. 绑定服务名称与类型

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 //添加这一行

其他两个文件和这两个文件的内容要保持完全一致!多一个换行一个空格都不行!!!

  1. 赋予第三方应用查找权限

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 白名单更新与全量编译

  1. 更新系统 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)
  1. 全量编译系统镜像
kotlin 复制代码
m -j$(nproc)
  1. 强制清空缓存并冷启动模拟器
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-apim 全编,总耗时在一个小时左右。

六、 第三方应用调用

因为默认的安卓 SDK 不包含我们自定义的服务和常量信息,尽管我们的系统已经编译成功了,但是 APP 编译的环境还是官方默认的,所以需要在项目中导入我们编译后的 class.jar 包,让 APP 可以编译通过。

  1. 引入定制的 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,否则仍编译不过。

  1. 业务代码调用
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

但并不是长久之计,目前我是没找到问题出在哪里,如果你知道的话,还望不吝赐教:)

相关推荐
程序员陆业聪4 小时前
AI编码提效实战:Skill、Rule与上下文工程
android
程序员陆业聪5 小时前
AI驱动需求梳理与Spec编写:让PRD自动变成技术方案
android
李艺为6 小时前
Android Studio使用switch匹配资源id时报需要常量表达式解决办法
android
YaBingSec8 小时前
玄机靶场-2024ccb初赛sc05 WP
android·运维·网络·笔记·安全·ssh
常利兵8 小时前
解锁Android嵌入式照片选择器,让你的App体验丝滑起飞
android
峥嵘life8 小时前
Android 切换用户后无法获取 MAC 地址分析解决
android·python·macos
JJay.9 小时前
Android BLE 为什么连上了却收不到数据
android
歪楼小能手9 小时前
Android16在开机向导最后添加一个声明界面
android·java·平板
夏沫琅琊9 小时前
Android联系人导入导出
android·kotlin