6、Android系统服务开发:AIDL接口定义、Service实现、权限管理
系统服务开发,说白了就是让你的App能跟系统底层“对话”。我刚开始接触这块时,总觉得AIDL这玩意儿很玄乎。后来做车载项目,需要在应用层控制底层的背光亮度,才真正体会到它的价值。
这一章,我们就来聊聊怎么定义AIDL接口、实现Service,以及如何做好权限管理。嗯,都是实战中绕不开的硬骨头。
6.1 AIDL接口定义——跨进程通信的“契约”
AIDL(Android Interface Definition Language)是Android跨进程通信的接口定义语言。你可以把它想象成一份合同——服务端和客户端都得按这个来。
为什么需要AIDL?
同一个App里,我们直接用Binder就行。但跨App通信时,AIDL就成了标准。我见过不少新手直接传复杂对象,结果崩溃了——因为没实现Parcelable接口。
来看一个实际例子。假设我们要做一个“系统亮度服务”,先定义AIDL接口:
// ILightService.aidl
package com.example.systemservice;
interface ILightService {
// 设置亮度值,范围0-255
void setBrightness(int value);
// 获取当前亮度
int getBrightness();
// 注册回调,亮度变化时通知客户端
void registerCallback(ILightCallback callback);
void unregisterCallback(ILightCallback callback);
}
这里有个细节——回调接口也得用AIDL定义。为什么呢?因为回调也是跨进程的。
// ILightCallback.aidl
package com.example.systemservice;
interface ILightCallback {
void onBrightnessChanged(int newValue);
}
我个人习惯把AIDL文件放在 src/main/aidl/ 目录下,包名跟Java代码保持一致。这样编译时能自动生成Stub和Proxy类,省心不少。
LightInfo.aidl 里写 parcelable LightInfo;。
6.2 Service实现——让接口“活”起来
AIDL接口定义好了,接下来就是实现它。Service是Android系统服务的载体,我们得在Service里把接口方法真正跑起来。
实现步骤:
- 创建一个Service子类
- 在Service内部实现AIDL接口的Stub
- 在
onBind()方法中返回Stub实例 - 在AndroidManifest.xml中注册Service
来看代码:
public class LightService extends Service {
private static final String TAG = "LightService";
private int mBrightness = 128; // 默认亮度
private RemoteCallbackList<ILightCallback> mCallbacks =
new RemoteCallbackList<>();
private final ILightService.Stub mBinder = new ILightService.Stub() {
@Override
public void setBrightness(int value) throws RemoteException {
// 检查权限
if (checkCallingPermission("com.example.permission.SET_BRIGHTNESS")
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("没有设置亮度的权限");
}
// 这里可以写底层驱动操作
mBrightness = value;
Log.d(TAG, "亮度设置为: " + value);
// 通知所有注册的客户端
int count = mCallbacks.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
mCallbacks.getBroadcastItem(i).onBrightnessChanged(value);
} catch (RemoteException e) {
Log.e(TAG, "回调失败", e);
}
}
mCallbacks.finishBroadcast();
}
@Override
public int getBrightness() throws RemoteException {
return mBrightness;
}
@Override
public void registerCallback(ILightCallback callback)
throws RemoteException {
mCallbacks.register(callback);
}
@Override
public void unregisterCallback(ILightCallback callback)
throws RemoteException {
mCallbacks.unregister(callback);
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
这里有个坑——RemoteCallbackList 是专门为跨进程回调设计的。我曾经直接用 ArrayList 存回调,结果客户端进程挂了,服务端还傻傻地调用,直接抛异常。用 RemoteCallbackList 会自动处理这些情况。
beginBroadcast() 和 finishBroadcast() 之间不要做耗时操作,否则会阻塞其他回调的发送。
别忘了在AndroidManifest.xml里注册:
<service
android:name=".LightService"
android:enabled="true"
android:exported="true"
android:process=":system_service">
</service>
把Service放在独立进程里,这是系统服务的常见做法。你想想看,如果服务挂了,至少不影响主App的运行。
6.3 权限管理——别让谁都来调你的服务
系统服务暴露给外部App,权限管理就是第一道防线。Android提供了两种方式:
- 声明式权限:在AndroidManifest中定义,客户端需要声明才能绑定
- 运行时检查:在Service方法中通过
checkCallingPermission()检查
定义权限:
<permission
android:name="com.example.permission.SET_BRIGHTNESS"
android:protectionLevel="signature" />
<permission
android:name="com.example.permission.GET_BRIGHTNESS"
android:protectionLevel="normal" />
protectionLevel 的选择很关键。我做过一个项目,把系统服务的权限设成了 normal,结果随便一个第三方App都能调用。后来改成了 signature,只有同签名的App才能访问,安全多了。
在Service中检查权限:
@Override
public void setBrightness(int value) throws RemoteException {
// 检查调用者是否有权限
if (checkCallingPermission("com.example.permission.SET_BRIGHTNESS")
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("权限不足");
}
// ... 执行操作
}
这里有个细节——checkCallingPermission() 检查的是调用者进程的权限,不是Service自身的权限。嗯,这个很容易搞混。
- 写操作用
signature级别权限 - 读操作用
normal级别权限 - 敏感操作一定要在方法内部二次检查
- 不要完全依赖AndroidManifest的权限声明
6.4 知识体系总览
说了这么多,我们来画一张图,把整个系统服务开发的脉络理清楚:
6.5 客户端如何调用
服务端写好了,客户端怎么用?其实很简单:
// 绑定服务
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.example.systemservice",
"com.example.systemservice.LightService"
));
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
// 连接回调
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mLightService = ILightService.Stub.asInterface(service);
try {
mLightService.setBrightness(200);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mLightService = null;
}
};
记得在AndroidManifest中声明权限:
<uses-permission android:name="com.example.permission.SET_BRIGHTNESS" />
onServiceConnected 里直接做UI操作,结果ANR了。跨进程调用是异步的,但回调是在Binder线程池里执行的,不能直接更新UI。记得用Handler切回主线程。
好了,系统服务开发的核心就这些。AIDL定义好接口,Service实现逻辑,权限管理守住大门。这三板斧用熟了,大部分系统服务场景都能搞定。
公众号:蓝海资料掘金营,微信deep3321