Android系统开发入门-7.添加java层系统服务

我们作为一个系统开发人员,经常要与各种服务打交道,什么 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 的权限后,重新编译运行虚拟机,就可以正常调用服务的接口了。