我们作为一个系统开发人员,经常要与各种服务打交道,什么 ActivityManagerService啊,PackageManagerService啊, PowerManagerService啊的。大部分场景是都只需要打打 patch 就行了, 但有时候我们也需要添加一些自定义的服务的。服务可以有很多种形式, 但这里讨论的是如何添加 java 层的系统服务。
1. 使用 aidl 定义服务接口 首先我们得定义我们的服务名是什么,提供什么样的接口。 在这个例子里面,我们的服务就叫 HelloService,它提供了下接口:
void hello(String name) //播放指定路径的音频文件
在 frameworks/base/core/java/android 目录下创建 pure 目录,我们把代码放到 pure 里面。 pure 目录下增加中文件 IHelloService.aidl
1 2 3 4 package android.pure; interface IHelloService { void hello(String name); }
在 frameworks/base/Android.bp framework-defaults 模块中添加我们刚刚加的 aidl 文件
1 "core/java/android/pure/IHelloService.aidl",
然后进入 framework/base 目录执行 mm -j 命令编译 framework.jar 模块。 编译成功后,会在 out/soong/.intermediates/frameworks/base/framework/android_common/gen/aidl/frameworks/base/core/java/android/pure 目录生成 IHelloService.java 这个文件。 这个文件封装了 binder 通信的相关细节,有兴趣的同学可以去了解一下。
2. 实现接口 在 frameworks/base/services/core/java/com/android/server 目录下新建 HelloService.java 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.android.server; import android.pure.IHelloService; import android.util.Log; public class HelloService extends IHelloService.Stub { private final String TAG = "HelloService"; public HelloService() { Log.d(TAG, "create hello service"); } @Override public void hello(String name) { Log.d(TAG, "hello " + name); } }
3. 将服务添加到 ServiceManager 修改 frameworks/base/services/java/com/android/server/SystemServer.java 文件,在 startOtherServices 方法里面增加以下代码:
1 2 3 4 // add hello service traceBeginAndSlog("HelloService"); ServiceManager.addService("HelloService", new HelloService()); traceEnd();
至此服务算是添加完了,我们来编译运行一下,发现系统起不来了,看下错误 log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 12-20 23:46:47.308 3521 3521 I SystemServer: HelloService 12-20 23:46:47.308 3521 3521 D HelloService: create hello service 12-20 23:46:47.308 1526 1526 E SELinux : avc: denied { add } for service=HelloService pid=3521 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0 12-20 23:46:47.308 1526 1526 E ServiceManager: add_service('HelloService',95) uid=1000 - PERMISSION DENIED 12-20 23:46:47.308 3521 3521 E System : ****************************************** 12-20 23:46:47.308 3521 3521 E System : ************ Failure starting system services 12-20 23:46:47.308 3521 3521 E System : java.lang.SecurityException 12-20 23:46:47.308 3521 3521 E System : at android.os.BinderProxy.transactNative(Native Method) 12-20 23:46:47.308 3521 3521 E System : at android.os.BinderProxy.transact(BinderProxy.java:510) 12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManagerProxy.addService(ServiceManagerNative.java:156) 12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManager.addService(ServiceManager.java:192) 12-20 23:46:47.308 3521 3521 E System : at android.os.ServiceManager.addService(ServiceManager.java:161) 12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.startOtherServices(SystemServer.java:1914) 12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.run(SystemServer.java:512) 12-20 23:46:47.308 3521 3521 E System : at com.android.server.SystemServer.main(SystemServer.java:349) 12-20 23:46:47.308 3521 3521 E System : at java.lang.reflect.Method.invoke(Native Method) 12-20 23:46:47.308 3521 3521 E System : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 12-20 23:46:47.308 3521 3521 E System : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:908) 12-20 23:46:47.308 3521 3521 D SystemServerTiming: HelloService took to complete: 1ms
从 SELinux : avc: denied { add } for service=HelloService
这个信息来看,是因为我们没有添加 SELinux 规则,什么是 SELinux 这里就不展开了,同学们可以自己去查下资料。
4. selinux 规则设置 Android 10 的 selinux 规则是放在 system/sepolicy 目录下的。 但 Android 每个版本 selinux 的添加规则多多少少是有些变化的,我们要怎么把这个新服务的 SELinux 规则给加上呢, 我们可以参考现有的系统服务的规则去添加,这里参考的是 network_time_update_service 服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cd system/sepolicy grep -nr network_time_update_service ... prebuilts/api/28.0/plat_pub_versioned.cil:2650:(type network_time_update_service) prebuilts/api/28.0/plat_pub_versioned.cil:2651:(typeattribute network_time_update_service_28_0) prebuilts/api/28.0/plat_pub_versioned.cil:2652:(roletype object_r network_time_update_service_28_0) prebuilts/api/28.0/public/service.te:107:type network_time_update_service, system_server_service, service_manager_type; prebuilts/api/28.0/private/compat/27.0/27.0.cil:385:(expandtypeattribute (network_time_update_service_27_0) true) prebuilts/api/28.0/private/compat/27.0/27.0.cil:1102:(typeattributeset network_time_update_service_27_0 (network_time_update_service)) prebuilts/api/28.0/private/compat/26.0/26.0.cil:389:(typeattributeset network_time_update_service_26_0 (network_time_update_service)) prebuilts/api/28.0/private/service_contexts:109:network_time_update_service u:object_r:network_time_update_service:s0 public/service.te:125:type network_time_update_service, system_server_service, service_manager_type; private/compat/27.0/27.0.cil:391:(expandtypeattribute (network_time_update_service_27_0) true) private/compat/27.0/27.0.cil:1108:(typeattributeset network_time_update_service_27_0 (network_time_update_service)) private/compat/26.0/26.0.cil:395:(typeattributeset network_time_update_service_26_0 (network_time_update_service)) private/compat/28.0/28.0.cil:463:(expandtypeattribute (network_time_update_service_28_0) true) private/compat/28.0/28.0.cil:1306:(typeattributeset network_time_update_service_28_0 (network_time_update_service)) private/service_contexts:132:network_time_update_service u:object_r:network_time_update_service:s0
涉及到的文件很多,有部分文件是不需要修改的,我们先把找到的所有 service.te 和 service_contexts 都参考 network_time_update_service 加上 HelloService 的配置。
service_contexts 加上
1 HelloService u:object_r:HelloService:s0
service.te 加上
1 type HelloService, system_server_service, service_manager_type;
5. 编译验证 完成以上各步骤的修改后,我们就可以先来验证一下,服务是否添加成功了。 先 make api-stubs-docs-update-current-api -j20
更新一下 api 接口,再整编系统。 运行虚拟机,执行 service list
看看有没有 HelloService 服务。
1 2 3 pure:/ # service list | grep HelloService 16 HelloService: [android.pure.IHelloService] pure:/ #
6. 客户端调用 我们创建完服务端之后,还要考虑怎么提供 api 接口给到 client 使用。我们分为两种情况来讨论。
6.1 系统源码中使用 假如我们上面添加的服务是给在系统源码中编译的模块使用的,那就简单了。比如一个 apk ,我们在系统源码中创建一个 apk 模块。目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models$ tree PureSettings/ PureSettings/ ├── Android.bp ├── AndroidManifest.xml ├── res │ ├── layout │ │ └── activity_main.xml │ └── mipmap-hdpi │ └── ic_launcher.png └── src └── com └── pure └── settings └── MainActivity.java 7 directories, 5 files qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models$
Android.bp:
1 2 3 4 5 6 7 android_app { srcs: ["src/**/*.java"], resource_dirs: ["res"], name: "PureSettings", certificate: "platform", platform_apis: true, }
其中 platform_apis 要设置为 true,不然没法调用我们新加的服务和 ServiceManager 。
AndroidManifest.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.pure.settings"> <application android:icon="@mipmap/ic_launcher" android:label="PureSettings" android:supportsRtl="true"> <activity android:name="com.pure.settings.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
res/layout/activity_main.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="test" /> </LinearLayout>
src/com/pure/settings/MainActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.pure.settings; import android.app.Activity; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.pure.IHelloService; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { private static final String TAG = "PureSettings"; private IHelloService service = null; private Button button; private void test() { Log.d(TAG, "test"); try { service.hello("qiushao"); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); service = IHelloService.Stub.asInterface(ServiceManager.getService("HelloService")); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { test(); } }); } }
把模块添加到系统, pure.mk:
1 PRODUCT_PACKAGES += PureSettings
编译运行,闪退了,看下错误日志,发现:
1 12-21 23:55:32.970 1526 1526 E SELinux : avc: denied { find } for service=HelloService pid=4266 uid=10102 scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:HelloService:s0 tclass=service_manager permissive=0
看起来是还有 selinux 权限需要添加。具体分析如下 缺少什么权限:{find}权限, 谁缺少权限:scontext=u:r:platform_app:s0:c512,c768 对哪个文件缺少权限:tcontext=u:object_r:HelloService:s0 什么类型的文件:tclass=service_manager
完整的意思: platform_app 缺少 service_manager 类型的 HelloService 的 find 权限。
根据以上分析,我们就可以找到解决方案: 在 platform_app.te 中添加 HelloService 的 find 权限即可。
1 allow platform_app HelloService:service_manager find;
我们查找一下有哪些 platform_app.te 文件:
1 2 3 4 5 6 7 8 9 10 11 qiushao@qiushao-pc:~/source/android-10/system/sepolicy$ find -name platform_app.te ./prebuilts/api/27.0/public/platform_app.te ./prebuilts/api/27.0/private/platform_app.te ./prebuilts/api/29.0/public/platform_app.te ./prebuilts/api/29.0/private/platform_app.te ./prebuilts/api/26.0/public/platform_app.te ./prebuilts/api/26.0/private/platform_app.te ./prebuilts/api/28.0/public/platform_app.te ./prebuilts/api/28.0/private/platform_app.te ./public/platform_app.te ./private/platform_app.te
把找到的所有 private/platform_app.te 都加上后,再重新编译运行发现不会挂了,看下 logcat:
1 2 3 130|pure:/ # logcat -c;logcat -s "HelloService:V" "PureSettings:V" 12-22 10:47:26.505 4291 4291 D PureSettings: test 12-22 10:47:26.506 1757 3514 D HelloService: hello qiushao
的确调用到了我们添加的服务。
6.2 非系统源码中使用 前面实现了在 Android 系统源码中调用新添加服务的方法,但在实际项目中很多情况是在非系统源码环境下使用的。这种情况我们一般是再封装一层接口出来。 调用的层级为: apk –> HelloApi –> HelloService 我们按以下目录结构创建 HelloApi 模块:
1 2 3 4 5 6 7 8 9 10 11 qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models$ tree HelloApi/ HelloApi/ ├── Android.bp └── java └── com └── pure └── api └── HelloManager.java 4 directories, 2 files qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models$
Android.bp:
1 2 3 4 5 6 7 java_library { name: "com.pure.api", installable: true, srcs: [ "java/**/*.java", //文件列表 ], }
HelloManager.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.pure.api; import android.os.RemoteException; import android.os.ServiceManager; import android.pure.IHelloService; public class HelloManager { private static HelloManager mInstance = null; public static HelloManager getInstance() { if (null == mInstance) { mInstance = new HelloManager(); } return mInstance; } private IHelloService mService = null; private HelloManager() { mService = IHelloService.Stub.asInterface(ServiceManager.getService("HelloService")); } public void sayHello(String name) { try { mService.hello(name); } catch (RemoteException e) { e.printStackTrace(); } } }
然后到 HelloApi 目录 mm 编译模块,得到 out/target/common/obj/JAVA_LIBRARIES/com.pure.api_intermediates/classes.jar 文件。 我们把这个 classes.jar 文件,复制到用 android-studio 创建的 apk 项目 的 app/libs 目录下,改名为 com.pure.api.jar 。 右键单击 com.pure.api.jar 选择 add as library, 然后我们就可以在 apk 的代码中使用 com.pure.api.jar 的接口了:
1 2 3 4 5 6 7 8 9 ... public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); HelloManager.getInstance().sayHello("qiushao"); } }
安装 apk 到虚拟机上运行,又遇到了 SELinux : avc: denied 错误,这次的错误是
E SELinux : avc: denied { find } for service=HelloService pid=4266 uid=10102 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:HelloService:s0 tclass=service_manager permissive=0
跟上面的错误基本上是一样的,只不过是把 platform_app 换成了 untrusted_app 而已。platform_app 表示有系统签名的 apk, untrusted_app 表示没有系统签名的 apk。 我们按同样的思路添加完 selinux 的权限后,重新编译运行虚拟机,就可以正常调用服务的接口了。