Android系统开发--12.添加java层系统服务

我们作为一个系统开发人员,经常要与各种服务打交道,什么 ActivityManagerService啊,PackageManagerService啊, PowerManagerService啊的。大部分场景是都只需要打打 patch 就行了, 但有时候我们也需要添加一些自定义的服务的。服务可以有很多种形式, 但这里讨论的是如何添加 java 层的系统服务。

1. 使用 aidl 定义服务接口

首先我们得定义我们的服务名是什么,提供什么样的接口。
在这个例子里面,我们的服务就叫 PurePlayerService,它提供了下接口:

  • void play(String path) //播放指定路径的音频文件
  • void stop() //停止播放
  • registerListener(IPurePlayerListener) //注册监听器,开始播放,停止播放时会通知监听器对应的事件

当然我们并不会真正的去播放音频文件,只是为了演示怎么添加服务而已。我们还定义了 registerListener 接口,用来演示怎么实现服务端回调客户端。
为了方便大家创建服务,Android 系统提供了 aidl(Android Interface definition language) 工具。它是一种 Android 内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。

在 frameworks/base/core/java/android 目录下创建 pure 目录,我们把代码放到 pure 里面。
pure 目录下增加中文件 IPurePlayerListener.aidl

1
2
3
4
5
package android.pure;
interface IPurePlayerListener {
void onPlay(String path);
void onStop();
}

还有 IPurePlayerService.aidl

1
2
3
4
5
6
7
8
package android.pure;
import android.pure.IPurePlayerListener;
interface IPurePlayerService {
void play(String path);
void stop();
void registerListener(IPurePlayerListener listener);
void unregisterListener(IPurePlayerListener listener);
}

在 frameworks/base/Android.mk 中LOCAL_SRC_FILES += \ 下添加我们刚刚加的 aidl 文件

1
2
3
LOCAL_SRC_FILES += \
core/java/android/pure/IPurePlayerListener.aidl \
core/java/android/pure/IPurePlayerService.aidl \

然后进入 framework/base 目录执行 mm 命令编译 framework.jar 模块。
编译成功后,会在 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/core/java/android/pure 目录生 IPurePlayerListener.java 和 IPurePlayerService.java 这两个文件。 这两个文件封装了 binder 通信的相关细节,有兴趣的同学可以去了解一下。

2. 实现接口

在 frameworks/base/services/core/java/com/android/server 目录下新建 PurePlayerService.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
42
43
44
45
46
47
48
49
50
package com.android.server;

import android.os.RemoteException;
import android.pure.IPurePlayerListener;
import android.pure.IPurePlayerService;
import android.util.Log;

import java.util.HashSet;
import java.util.Set;

public class PurePlayerService extends IPurePlayerService.Stub {

private final String TAG = "PurePlayerService";
private Set<IPurePlayerListener> mListeners;
private String mPath;

public PurePlayerService() {
Log.i(TAG, "new PurePlayerService");
mListeners = new HashSet<>();
}

@Override
public void play(String path) throws RemoteException {
Log.i(TAG, "playing " + path);
mPath = path;
for (IPurePlayerListener listener : mListeners) {
listener.onPlay(path);
}
}

@Override
public void stop() throws RemoteException {
Log.i(TAG, "stop play " + mPath);
for (IPurePlayerListener listener : mListeners) {
listener.onStop();
}
}

@Override
public void registerListener(IPurePlayerListener listener) throws RemoteException {
Log.i(TAG, "register listener " + listener);
mListeners.add(listener);
}

@Override
public void unregisterListener(IPurePlayerListener listener) throws RemoteException {
Log.i(TAG, "unregister listener " + listener);
mListeners.remove(listener);
}
}

3. 将服务添加到 ServiceManager

修改 frameworks/base/services/java/com/android/server/SystemServer.java 文件,在 startOtherServices 方法里面增加以下代码:

1
2
3
4
// pure player service
traceBeginAndSlog("PurePlayerService");
ServiceManager.addService("PurePlayerService", new PurePlayerService());
traceEnd();

4. selinux 规则设置

Android-8.1 的 selinux 规则是放在 system/sepolicy 目录下的。
找了很多资料,不知道怎么在 $product 目录下添加 sepolicy,所以直接在 system/sepolicy 下修改了。
规则很复杂,一开始无从下手,但我们可以参考现有的系统服务的规则去添加,这里参考的是 netstats 服务:

1
2
cd system/sepolicy
grep -nr netstats

把 netstats 有的配置都复制一下,改一下服务名就行了。
最后修改了以下文件:

1
2
3
4
5
6
7
8
9
10
11
12
qiushao@qiushao-PC:~/source/android-8.1/system/sepolicy$ git status
当前不在任何分支上。
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)

修改: prebuilts/api/26.0/nonplat_sepolicy.cil
修改: prebuilts/api/26.0/private/service_contexts
修改: prebuilts/api/26.0/public/service.te
修改: private/compat/26.0/26.0.cil
修改: private/service_contexts
修改: public/service.te

Android selinux 的规则太复杂了,而且每个版本的的规则又有变化,后面有时间再专门去整理一下才行。

5. 编译验证

完成以上各步骤的修改后,我们就可以先来验证一下,服务是否添加成功了。
make update-api 更新一下 api 接口,再整编系统。
运行虚拟机,执行 service list 看看有没有 PurePlayerService 服务。

1
2
generic_pure:/ # service list | grep -i pure                                                                                                            
12 PurePlayerService: [android.pure.IPurePlayerService]

6. 客户端调用

在系统源码中创建一个 PureTest 应用
布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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:text="play"
android:onClick="play"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<Button
android:text="stop"
android:onClick="stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package net.qiushao.pure.test;

import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.pure.IPurePlayerListener;
import android.pure.IPurePlayerService;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

private static final String TAG = "PureTest";
private IPurePlayerService purePlayer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
purePlayer = IPurePlayerService.Stub.asInterface(ServiceManager.getService("PurePlayerService"));
try {
purePlayer.registerListener(listener);
} catch (RemoteException e) {
Log.e(TAG,"register listener error");
e.printStackTrace();
}
}

IPurePlayerListener listener = new IPurePlayerListener.Stub() {
@Override
public void onPlay(String path) throws RemoteException {
Toast.makeText(MainActivity.this, "start to play " + path, Toast.LENGTH_LONG).show();
}

@Override
public void onStop() throws RemoteException {
Toast.makeText(MainActivity.this, "stop play", Toast.LENGTH_LONG).show();
}
};

public void play(View view) {
try {
purePlayer.play("just for today");
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void stop(View view) {
try {
purePlayer.stop();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

Android.mk

1
2
3
4
5
6
7
8
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PureTest
LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES := framework
include $(BUILD_PACKAGE)

这里只是最简单的模型而已,还有很多问题没有处理,比如 server 或者 client GG 了怎么办?多线程并发访问如何处理。
这些问题后面再讨论。