15、系统服务定制:添加自定义系统服务、系统服务注册与权限管理、跨进程通信(AIDL)
说实话,系统服务是Android系统的灵魂。没有服务,应用就是个空壳子。在车载场景下,我们经常需要定制一些系统级服务——比如车辆状态监控、空调控制、座椅调节等等。这些服务需要常驻后台,还要能被多个应用同时调用。
今天我们就来聊聊,怎么在Android Automotive里添加一个自定义系统服务。我会把注册流程、权限管理、还有AIDL跨进程通信都串起来讲。嗯,这部分内容我踩过不少坑,希望能帮你少走弯路。
15.1 系统服务架构概览
先看一张图,理解系统服务在整个Android系统中的位置。
从这张图可以看到,应用层通过AIDL接口调用Framework层的系统服务,而系统服务最终会跟HAL层打交道。我个人的习惯是,先把这张图印在脑子里,再动手写代码。
15.2 定义AIDL接口
跨进程通信的核心就是AIDL。说白了,AIDL就是定义了一个契约——服务端和客户端都按这个契约来通信。
我们先定义一个车辆状态服务的AIDL接口:
// IVehicleService.aidl
package com.example.vehicle;
interface IVehicleService {
// 获取车辆速度
int getVehicleSpeed();
// 设置空调温度
void setAcTemperature(int temperature);
// 注册车辆状态监听
void registerCallback(IVehicleCallback callback);
// 注销监听
void unregisterCallback(IVehicleCallback callback);
}
再定义一个回调接口,用于服务端主动推送数据给客户端:
// IVehicleCallback.aidl
package com.example.vehicle;
oneway interface IVehicleCallback {
void onSpeedChanged(int speed);
void onAcTemperatureChanged(int temperature);
}
15.3 实现系统服务端
服务端实现,说白了就是写一个继承自Binder的类,然后把AIDL接口里的方法都实现一遍。
// VehicleService.java
public class VehicleService extends IVehicleService.Stub {
private static final String TAG = "VehicleService";
private int mSpeed = 0;
private int mAcTemp = 24;
private RemoteCallbackList<IVehicleCallback> mCallbacks =
new RemoteCallbackList<>();
@Override
public int getVehicleSpeed() {
return mSpeed;
}
@Override
public void setAcTemperature(int temperature) {
mAcTemp = temperature;
// 通知所有注册的客户端
int count = mCallbacks.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
mCallbacks.getBroadcastItem(i).onAcTemperatureChanged(mAcTemp);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
}
mCallbacks.finishBroadcast();
}
@Override
public void registerCallback(IVehicleCallback callback) {
mCallbacks.register(callback);
}
@Override
public void unregisterCallback(IVehicleCallback callback) {
mCallbacks.unregister(callback);
}
}
15.4 注册系统服务到ServiceManager
服务写好了,怎么让应用能找到它?答案是注册到ServiceManager。这个过程在SystemServer里完成。
// SystemServer.java (部分代码)
private void startVehicleService() {
try {
VehicleService service = new VehicleService();
ServiceManager.addService("vehicle", service);
Log.i(TAG, "VehicleService registered successfully");
} catch (Throwable e) {
Log.e(TAG, "Failed to register VehicleService", e);
}
}
嗯,这里要注意一个细节:服务名称"vehicle"是全局唯一的。我建议命名时加上公司前缀,比如"com.example.vehicle",避免跟其他厂商的服务冲突。
15.5 权限管理
系统服务不能谁都能调,对吧?我们需要做权限控制。Android提供了两种方式:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| AndroidManifest权限 | 在服务端声明权限,客户端需要申请 | 普通系统服务 |
| 签名权限 | 只有相同签名的应用才能调用 | 核心系统服务 |
| 运行时权限校验 | 在服务方法中动态检查 | 需要细粒度控制的场景 |
我推荐的做法是:在服务端每个方法里都做权限校验。这样最安全。
// 在VehicleService中添加权限校验
private void checkPermission() {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
// 检查调用者是否有权限
if (mContext.checkPermission(
"com.example.vehicle.ACCESS_VEHICLE_DATA",
pid, uid) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("No permission to access vehicle data");
}
}
@Override
public int getVehicleSpeed() {
checkPermission(); // 先校验权限
return mSpeed;
}
15.6 客户端调用
客户端怎么用?其实很简单,通过ServiceManager拿到服务代理,然后调用方法就行。
// 客户端代码
public class VehicleClient {
private IVehicleService mService;
public void connect() {
// 获取服务代理
IBinder binder = ServiceManager.getService("vehicle");
if (binder != null) {
mService = IVehicleService.Stub.asInterface(binder);
}
}
public int getSpeed() {
if (mService != null) {
try {
return mService.getVehicleSpeed();
} catch (RemoteException e) {
Log.e(TAG, "Remote call failed", e);
}
}
return -1;
}
}
你想想看,客户端拿到的其实是一个Binder代理对象,真正的服务在SystemServer进程里。每次调用都是一次跨进程通信,所以要注意性能问题。
15.7 避坑指南
这部分我踩过的坑比较多,挑几个典型的说说:
- Binder线程池耗尽:系统默认的Binder线程池只有16个线程。如果你的服务被大量并发调用,线程池会耗尽,导致其他服务也受影响。解决方案是:耗时操作一定要异步处理。
- 回调泄漏:客户端注册了回调但忘记注销,服务端会一直持有Binder引用。我曾经排查过一个内存泄漏问题,查了两天才发现是回调没注销。用RemoteCallbackList可以自动处理,但客户端最好还是在onDestroy里主动注销。
- 权限声明位置:自定义权限要在frameworks/base/core/res/AndroidManifest.xml里声明,而不是在你的服务模块里。否则系统不认这个权限。
- AIDL接口变更:AIDL接口一旦发布,尽量不要修改方法签名。如果非要改,记得保持向后兼容。我见过有人删了一个方法,结果所有客户端都崩溃了。
15.8 调试技巧
调试系统服务,最常用的命令就是dumpsys。你可以在服务端实现dump方法:
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("VehicleService State:");
writer.println(" Speed: " + mSpeed);
writer.println(" AC Temp: " + mAcTemp);
writer.println(" Callback count: " + mCallbacks.getRegisteredCallbackCount());
}
然后通过adb命令查看:
adb shell dumpsys vehicle
这个命令在调试时特别有用。我每次写完服务,第一件事就是验证dumpsys能不能正常输出。
15.9 总结
自定义系统服务其实不复杂,核心就三步:定义AIDL接口、实现服务端、注册到ServiceManager。但细节决定成败——权限管理、回调处理、线程安全,这些才是真正考验功底的地方。
嗯,如果你在车载项目里需要添加系统服务,建议先从简单的开始,比如只暴露几个get/set方法。等跑通了整个流程,再逐步增加复杂功能。这样出问题了也好定位。
最后说一句:系统服务是常驻进程的,一旦崩溃会影响整个系统。所以代码质量一定要过硬,该加的try-catch一个都不能少。
公众号:蓝海资料掘金营,微信deep3321