系统定制服务-IPC实战-系统新增aidl接口-实现自定义系统服务功能
Framework层定义aidl 接口,实现自定义系统服务
前言-需求场景
这里直接将客户需求拿过来吧,其实这种需求很常见,就是自定义一个服务,这个服务承担一定的功能。
系统源码需新增com.android.device.IDeviceCtrlService接口和com.android.device.IDeviceCtrlService.Stub实现类,提供"DeviceCtrlService"服务。
应用层通过IDeviceCtrlService mService = Stub.asInterface(ServiceManager.getService("DeviceCtrlService"))获取到设备设置的服务。
com.android.device.IDeviceCtrlService 接口内容如下:

一、涉及到的知识点
自己总结如下:
- aidl 相关知识点
- Framework层 aidl 相关的服务、接口定义等概念和具体实践
- 在Framework层自定义服务并在SystemServer 中添加自定义的服务
- SELinux 权限相关知识,让自己自定义的服务可以被系统服务添加且具备权限
- 应用层如何使用自己的服务
二、修改文件-新增文件-修改总览
修改文件
device/mediatek/sepolicy/basic/non_plat/device.te
device/mediatek/sepolicy/basic/non_plat/system_server.te
device/mediatek/sepolicy/basic/plat_private/service_contexts
frameworks/base/core/res/AndroidManifest.xml
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/api/current.txt
新增文件
frameworks/base/services/core/java/com/android/device/DeviceCtrlService.java
frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.aidl
frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.java
修改总览
create mode 100755 alps-release-s0.mp1.rc/device/mediatek/sepolicy/basic/non_plat/device.te
create mode 100755 alps-release-s0.mp1.rc/device/mediatek/sepolicy/basic/non_plat/system_server.te
create mode 100755 alps-release-s0.mp1.rc/device/mediatek/sepolicy/basic/plat_private/service_contexts
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/core/res/AndroidManifest.xml
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/services/api/current.txt
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/services/core/java/com/android/device/DeviceCtrlService.java
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.aidl
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.java
create mode 100755 alps-release-s0.mp1.rc/frameworks/base/services/java/com/android/server/SystemServer.java

三、修改方案实现
framework 功能接口实现
定义接口
路径: frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.aidl , 就是一个aidl 文件,用来定义接口用的
package com.android.device;
interface IDeviceCtrlService{
String getAudioOutList();
void setCurrentAudioOut(String key);
String getCurrentAudioOut();
void setAudioOutAuto(boolean value);
void setCecStatus(String value);
String getCecStatus();
void setLedStatus(String value);
String getLedStatus();
void setStandbyStatus(String value);
String getStandbyStatus();
void setIpRouteInfos(in String[] ipv4_infos,in String[] ipv6_infos);
void setPackageRouteInfos(String packageInfoJson);
void setConnectToken(String token);
String getConnectToken();
String getGlobalAppInstallPermissions();
}
定义接口实现类
路径:frameworks/base/services/core/java/com/android/device/DeviceCtrlService.java , 就是自己定义的服务,实现了上面定义的接口,就是一个服务类,在aidl 体系中兼任的就是一个server 端,处理具体业务工作的。
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.device;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.device.IDeviceCtrlService;
import com.android.server.SystemService;
import com.android.server.pm.PackageManagerService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Device Control Service implementation
* Provides device settings and control functionalities
*/
public class DeviceCtrlService extends SystemService {
private static final String TAG = "DeviceCtrlService";
private static final boolean DEBUG = true;
Context mContext;
// Device state storage
private String mCurrentAudioOut = "hdmi";
private boolean mAudioOutAuto = true;
private String mCecStatus = "enabled";
private String mLedStatus = "auto";
private String mStandbyStatus = "immediate";
private String mConnectToken = "";
// Routing information
private List<String> mIpv4Infos = new ArrayList<>();
private List<String> mIpv6Infos = new ArrayList<>();
private String mPackageRouteInfo = "{}";
// Audio output options
private static final Map<String, String> AUDIO_OUTPUTS = new HashMap<>();
static {
AUDIO_OUTPUTS.put("hdmi", "HDMI Audio");
AUDIO_OUTPUTS.put("spdif", "S/PDIF Audio");
AUDIO_OUTPUTS.put("analog", "Analog Audio");
AUDIO_OUTPUTS.put("bluetooth", "Bluetooth Audio");
}
private final IDeviceCtrlService.Stub mServiceStub = new IDeviceCtrlService.Stub() {
@Override
public String getAudioOutList() {
final long token = Binder.clearCallingIdentity();
try {
StringBuilder sb = new StringBuilder();
sb.append("[");
boolean first = true;
for (Map.Entry<String, String> entry : AUDIO_OUTPUTS.entrySet()) {
if (!first) {
sb.append(",");
}
sb.append("{\"key\":\"").append(entry.getKey())
.append("\",\"name\":\"").append(entry.getValue()).append("\"}");
first = false;
}
sb.append("]");
return sb.toString();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setCurrentAudioOut(String key) {
final long token = Binder.clearCallingIdentity();
try {
if (AUDIO_OUTPUTS.containsKey(key)) {
mCurrentAudioOut = key;
Slog.i(TAG, "Audio output set to: " + key);
// Here you would typically call native methods to actually switch audio
// native_setAudioOutput(key);
} else {
Slog.w(TAG, "Invalid audio output key: " + key);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public String getCurrentAudioOut() {
final long token = Binder.clearCallingIdentity();
try {
return mCurrentAudioOut;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setAudioOutAuto(boolean value) {
final long token = Binder.clearCallingIdentity();
try {
mAudioOutAuto = value;
Slog.i(TAG, "Audio auto switch set to: " + value);
// Apply auto-switch setting
// native_setAudioAutoSwitch(value);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setCecStatus(String value) {
final long token = Binder.clearCallingIdentity();
try {
if (value.equals("enabled") || value.equals("disabled")) {
mCecStatus = value;
Slog.i(TAG, "CEC status set to: " + value);
// Apply CEC setting
// native_setCecStatus(value);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public String getCecStatus() {
final long token = Binder.clearCallingIdentity();
try {
return mCecStatus;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setLedStatus(String value) {
final long token = Binder.clearCallingIdentity();
try {
if (value.equals("on") || value.equals("off") || value.equals("auto")) {
mLedStatus = value;
Slog.i(TAG, "LED status set to: " + value);
// Apply LED setting
// native_setLedStatus(value);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public String getLedStatus() {
final long token = Binder.clearCallingIdentity();
try {
return mLedStatus;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setStandbyStatus(String value) {
final long token = Binder.clearCallingIdentity();
try {
if (value.equals("immediate") || value.equals("delay") || value.equals("never")) {
mStandbyStatus = value;
Slog.i(TAG, "Standby status set to: " + value);
// Apply standby setting
// native_setStandbyStatus(value);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public String getStandbyStatus() {
final long token = Binder.clearCallingIdentity();
try {
return mStandbyStatus;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setIpRouteInfos(String[] ipv4_infos, String[] ipv6_infos) {
final long token = Binder.clearCallingIdentity();
try {
if (ipv4_infos != null) {
mIpv4Infos = Arrays.asList(ipv4_infos);
}
if (ipv6_infos != null) {
mIpv6Infos = Arrays.asList(ipv6_infos);
}
Slog.i(TAG, "IP routes updated - IPv4: " + mIpv4Infos.size() +
", IPv6: " + mIpv6Infos.size());
// Apply IP routing configuration
// native_setIpRoutes(mIpv4Infos, mIpv6Infos);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setPackageRouteInfos(String packageInfoJson) {
final long token = Binder.clearCallingIdentity();
try {
mPackageRouteInfo = packageInfoJson;
Slog.i(TAG, "Package route info updated: " + packageInfoJson);
// Apply package routing configuration
// native_setPackageRoutes(packageInfoJson);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setConnectToken(String token) {
final long token_ = Binder.clearCallingIdentity();
try {
mConnectToken = token;
Slog.i(TAG, "Connect token updated");
// Store token securely
// saveConnectToken(token);
} finally {
Binder.restoreCallingIdentity(token_);
}
}
@Override
public String getConnectToken() {
final long token = Binder.clearCallingIdentity();
try {
// For security, token might be retrieved from secure storage
return mConnectToken;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public String getGlobalAppInstallPermissions() {
final long token = Binder.clearCallingIdentity();
try {
PackageManagerService pm = (PackageManagerService)
mContext.getSystemService("package");
// mContext.getSystemService(Context.PACKAGE_SERVICE);
// Build permissions info
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"install_non_market_apps\":").append(isNonMarketAppsAllowed());
sb.append(",\"verify_apps\":").append(isVerifyAppsEnabled());
sb.append("}");
return sb.toString();
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean isNonMarketAppsAllowed() {
return android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
android.provider.Settings.Global.INSTALL_NON_MARKET_APPS, 0) != 0;
}
private boolean isVerifyAppsEnabled() {
return android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
"package_verifier_enable", 1) != 0;
// android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) != 0;
}
};
public DeviceCtrlService(Context context) {
super(context);
Slog.i(TAG, "DeviceCtrlService started");
}
@Override
public void onStart() {
Slog.i(TAG, "DeviceCtrlService onStart");
publishBinderService("DeviceCtrlService", mServiceStub);
android.util.Slog.i("DeviceCtrlService", "✅ DeviceCtrlService regist MTK SystemService success");
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// Service is ready
Slog.i(TAG, "DeviceCtrlService boot phase: system services ready");
} else if (phase == PHASE_BOOT_COMPLETED) {
// Boot completed
Slog.i(TAG, "DeviceCtrlService boot completed");
}
}
}
生成aidl编译后的Java文件-桥梁
路径:frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.java ,就是一个中间类,通信桥梁,代理类。
这里就是接口和服务端之间的代理类,本身是动态生成的,但是为了源码好编译,这里是事先通过命令编译好然后放到这里,不然 源码编译的时候找不到 类。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.android.device;
public interface IDeviceCtrlService extends android.os.IInterface
{
/** Default implementation for IDeviceCtrlService. */
public static class Default implements com.android.device.IDeviceCtrlService
{
...................
public static final java.lang.String DESCRIPTOR = "com.android.device.IDeviceCtrlService";
public java.lang.String getAudioOutList() throws android.os.RemoteException;
public void setCurrentAudioOut(java.lang.String key) throws android.os.RemoteException;
public java.lang.String getCurrentAudioOut() throws android.os.RemoteException;
public void setAudioOutAuto(boolean value) throws android.os.RemoteException;
public void setCecStatus(java.lang.String value) throws android.os.RemoteException;
public java.lang.String getCecStatus() throws android.os.RemoteException;
public void setLedStatus(java.lang.String value) throws android.os.RemoteException;
public java.lang.String getLedStatus() throws android.os.RemoteException;
public void setStandbyStatus(java.lang.String value) throws android.os.RemoteException;
public java.lang.String getStandbyStatus() throws android.os.RemoteException;
public void setIpRouteInfos(java.lang.String[] ipv4_infos, java.lang.String[] ipv6_infos) throws android.os.RemoteException;
public void setPackageRouteInfos(java.lang.String packageInfoJson) throws android.os.RemoteException;
public void setConnectToken(java.lang.String token) throws android.os.RemoteException;
public java.lang.String getConnectToken() throws android.os.RemoteException;
public java.lang.String getGlobalAppInstallPermissions() throws android.os.RemoteException;
}
.....................
将自定义服务添加到系统中释放接口供访问
基本常识是自定义服务需要扔给交给SystemServer 类,注册服务启动的。 这样应用端才能够使用。
路径: frameworks/base/services/java/com/android/server/SystemServer.java ,
// modify by fangchen start
import com.android.device.DeviceCtrlService;
// modify by fangchen end
在合适的位置,启动自定义服务:
// modify by fangchen start
t.traceBegin("StartDeviceCtrlService");
try {
Slog.i(TAG, "Device Control Service");
mSystemServiceManager.startService(DeviceCtrlService.class);
} catch (Throwable e) {
Slog.i(TAG, "Device Control Service error");
e.printStackTrace();
reportWtf("starting DeviceCtrlService", e);
}
t.traceEnd();
// modify by fangchen end
修改 SELinux 规则,允许 system_server 添加自定义服务(核心)
新增服务名的 SELinux 类型(定义客体标签)
路径: device/mediatek/sepolicy/basic/non_plat/device.te
# modify by fangchen start custom Service
#type DeviceCtrlService, service_manager_type;
type device_ctrl_service, service_manager_type;
# modify by fangchen end custom Service

设置允许规则-允许SystemManager 具备add 权限
路径: device/mediatek/sepolicy/basic/non_plat/system_server.te
# modify by fangchen start custom Service
#type DeviceCtrlService, service_manager_type;
allow system_server device_ctrl_service:service_manager add;
# modify by fangchen end custom Service

权限服务映射-Framework层添加的服务映射到SELinux 权限名映射
路径: device/mediatek/sepolicy/basic/plat_private/service_contexts
# modify by fangchen start custom service
DeviceCtrlService u:object_r:device_ctrl_service:s0
# modify by fangchen end custom service

配置自己定义的服务
路径:frameworks/base/core/res/AndroidManifest.xml
将自己定义的服务,新增到配置文件中,如下:
<!-- modify by fangchen start -->
<service
android:name="com.android.device.DeviceCtrlService"
android:exported="true"/>
<!-- modify by fangchen end -->
新增更新的api
路径: frameworks/base/services/api/current.txt
更新了服务相关接口,那么就要更新自己的api , 这里 是更新之后的,一般都放到分支代码中,这样就可以直接编译了。
四、延伸知识点-技能点【特别特别重要】
必备知识点
如上已经总结了需求涉及到的知识点;需求的视线步骤。最基本必须掌握的。这里再次罗列下:
aidl 相关
aidl 文件接口自己写 -> 自己写实现类 aidl 的实现类,也就是一个服务 -> aidl 一定要生成一个aidl 代理的服务通信中间件。 在系统里面不建议编译时候自动生成,而是先用命令自己生成一次 -> 集成到SystemServer 服务管理里面去-> 编译更新api 让aidl 被系统识别 更新系统api ->编译打包固件
系统SeLinux 权限相关
系统服务SystemServer 还有好多系统机制每隔Android 平台的机制一样规则不一样,说白了就是Linux 权限 系统权限一说,规定了 不是你随便改就像改想添加就添加到系统里面去的。这个时候就有SeLinux 权限一说了,就是要把自己定制的服务添加到底层系统里面去,底层系统才能够监听到。 自定义服务 添加SeLinux 权限 保证三点:
- 定义自定义服务名的SELinux类型(推荐用小写,和服务名一致),案例:
type device_ctrl_service, service_manager_type; - 允许system_server添加该服务(核心规则),案例:
allow system_server device_ctrl_service:service_manager add; - 关联服务名到 SELinux 类型(关键),案例:
DeviceCtrlService u:object_r:device_ctrl_service:s0
当然,针对不同的Android 平台芯片平台、不同Android版本,配置的方式 文件路径不一致,适当修改即可。
必备技能点
增删改接口方法后如何生成aidl代理文件
package com.android.device;
import java.util.List;
interface IDeviceCtrlService{
String getAudioOutList();
void setCurrentAudioOut(String key);
String getCurrentAudioOut();
void setAudioOutAuto(boolean value);
void setCecStatus(String value);
String getCecStatus();
void setLedStatus(String value);
String getLedStatus();
void setStandbyStatus(String value);
String getStandbyStatus();
void setIpRouteInfos(in String[] ipv4_infos,in String[] ipv6_infos);
void setPackageRouteInfos(String packageInfoJson);
void setConnectToken(String token);
String getConnectToken();
String getGlobalAppInstallPermissions();
void setGlobalAppInstallPermissions(String status);
List<String> getPreInstallPackages();
}

在源码根目录编写脚本,生成新的aidl 中间文件,如下:
脚本如下: aidl_create.sh
mkdir -p frameworks/base/services/core/java/com/android/device/gen/
out_sys/soong/host/linux-x86/bin/aidl \
-o frameworks/base/services/core/java/com/android/device/gen/ \
-I frameworks/base/core/aidl/ \
-I frameworks/base/services/core/java/ \
--lang=java \
frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.aidl
cp frameworks/base/services/core/java/com/android/device/gen/com/android/device/IDeviceCtrlService.java frameworks/base/services/core/java/com/android/device/
然后执行脚本生成中间文件:
./aidl_create.sh
看一下实际结果:
查看 IDeviceCtrlService.java 有了新的方法了
更新系统api
以下三个命令很重要, source 准备当前环境,lunch 当前自己的平台项目分支【一般自己的配置文件、编译文件有的】;最后更新api ,如果这个不知道命令,在编译阶段会报错提醒您执行哪个命令的 m services-non-updatable-stubs-update-current-api
source build/envsetup.sh
lunch full_k69v1_64_k419-userdebug
m services-non-updatable-stubs-update-current-api
相关命令
aidl 命令
如上我们生成根据aidl 生成中间代理文件时候,用到了这个命令:out_sys/soong/host/linux-x86/bin/aidl ,所以在你编译源码之后会生成这个文件 这个环境。 然后再进行 生成工作
mkdir -p frameworks/base/services/core/java/com/android/device/gen/
out_sys/soong/host/linux-x86/bin/aidl \
-o frameworks/base/services/core/java/com/android/device/gen/ \
-I frameworks/base/core/aidl/ \
-I frameworks/base/services/core/java/ \
--lang=java \
frameworks/base/services/core/java/com/android/device/IDeviceCtrlService.aidl
cp frameworks/base/services/core/java/com/android/device/gen/com/android/device/IDeviceCtrlService.java frameworks/base/services/core/java/com/android/device/
adb 相关命令
service list | grep device_ctrl
# 输出:device_ctrl: [com.android.device.IDeviceCtrlService] → 成功
列出所有已注册的系统服务(关键)
service list | grep -i device
# 或直接搜索你注册的服务名
service list | grep -i DeviceCtrlService
service list | grep -i device_ctrl
# 1. 查看系统中所有已注册的服务(关键:找你的服务名)
adb shell service list | grep -i device
# 正确输出示例:device_ctrl: [com.android.device.IDeviceCtrlService]
# 若无输出 → 服务未注册;若有输出但名称不是"DeviceCtrlService" → 服务名不匹配
# 2. 查看SystemServer日志,确认服务是否注册成功
adb logcat -s SystemServer | grep -i DeviceCtrlService
# 正确输出:I SystemServer: ✅ DeviceCtrlService 注册成功!
# 若输出错误栈 → 注册代码执行失败;若无输出 → 注册代码未执行
# 3. 检查应用是否有系统签名(MTK必查)
adb shell dumpsys package 你的应用包名 | grep -i signature
# 正确输出:signatures=PackageSignatures{xxx [AndroidPlatformSigner]}
# 若不是系统签名 → 权限校验失败,无法访问系统服务
五、应用层验证
部分代码调用如下:
private IDeviceCtrlService mDeviceCtrlService;
@Override
protected void onCreate(Bundle savedInstanceState) {
..........
// 1. 获取系统服务
getDeviceCtrlService();
// 2. 调用服务方法
if (mDeviceCtrlService != null) {
callServiceMethods();
}
.........................
}
```java
/**
* 获取 DeviceCtrlService 服务实例
*/
private void getDeviceCtrlService() {
try {
// 通过 ServiceManager 获取服务,参数为注册的服务名
mDeviceCtrlService = IDeviceCtrlService.Stub.asInterface(ServiceManager.getService("DeviceCtrlService"));
if (mDeviceCtrlService == null) {
Log.e(TAG, "获取 DeviceCtrlService 失败:服务未注册或不存在");
try {
Class<?> smClass = Class.forName("android.os.ServiceManager");
Method listServices = smClass.getMethod("listServices");
String[] services = (String[]) listServices.invoke(null);
Log.d(TAG, "系统所有服务:" + Arrays.toString(services));
} catch (Exception e) {
e.printStackTrace();
}
} else {
Log.d(TAG, "获取 DeviceCtrlService 成功");
}
} catch (Exception e) {
Log.e(TAG, "获取 DeviceCtrlService 异常:", e);
}
}
/**
* 调用服务的具体方法
*/
private void callServiceMethods() {
try {
// 示例 1:获取音频输出列表
String audioOutList = mDeviceCtrlService.getAudioOutList();
Log.d(TAG, "音频输出列表:" + audioOutList);
// 示例 2:设置当前音频输出为 HDMI
mDeviceCtrlService.setCurrentAudioOut("hdmi");
// 获取设置后的音频输出
String currentAudioOut = mDeviceCtrlService.getCurrentAudioOut();
Log.d(TAG, "当前音频输出:" + currentAudioOut);
// 示例 3:设置 CEC 状态
mDeviceCtrlService.setCecStatus("enabled");
String cecStatus = mDeviceCtrlService.getCecStatus();
Log.d(TAG, "CEC 状态:" + cecStatus);
// 示例 4:设置 IP 路由信息
String[] ipv4Infos = new String[]{"192.168.1.1/24", "10.0.0.1/8"};
String[] ipv6Infos = new String[]{"2001:0db8:85a3:0000:0000:8a2e:0370:7334/64"};
mDeviceCtrlService.setIpRouteInfos(ipv4Infos, ipv6Infos);
// 示例 5:设置连接 Token
mDeviceCtrlService.setConnectToken("abc123456789");
String token = mDeviceCtrlService.getConnectToken();
Log.d(TAG, "连接 Token:" + token);
} catch (RemoteException e) {
// 跨进程调用必须捕获 RemoteException(服务崩溃、断开等场景会抛出)
Log.e(TAG, "调用服务方法异常(RemoteException):", e);
} catch (SecurityException e) {
// 权限不足时抛出
Log.e(TAG, "调用服务方法无权限:", e);
} catch (Exception e) {
Log.e(TAG, "调用服务方法未知异常:", e);
}
}
实验结果如下:


# 总结
这里的需求其实就是新增一个服务供本地App调用,涉及到知识点两点:
- aidl 一套环境配置、Framework层的接口实现
- SeLinux 权限机制
这个过程中各个步骤异常痛苦,各种报错,耐心解决每一个问题,然后得到最优解决方案至实现需求即可。
更多推荐


所有评论(0)