Android 添加系统服务的完整流程SystemService(史上最全)


前言

系统服务是Android中非常重要的一部分, 像ActivityManagerService, PackageManagerSersvice, WindowManagerService, 这些系统服务都是Framework层的关键服务, 本篇文章主要讲一下如何基于Android源码添加一个系统服务的完整流程, 除了添加基本系统服务, 其中还包含添加App通过AIDL调用的演示Demo, 包含App调用服务端, 也包含服务端回调App, 也就是完成一个简单的binder双向通信。
通过本章,以下知识应该了解和掌握:

aidl的使用
系统服务的注册与使用
初识Selinux

: 测试代码基于Android 10, 其他Android版本大同小异.

一、编写AIDL文件

添加服务首先是编写AIDL文件, AIDL文件路径如下:
frameworks/base/core/java/com/android/henryservice
henryInterface.aidl 内容如下:

// henryInterface.aidl
package com.android.henryservice;

// Declare any non-default types here with import statements
import com.android.henryservice.Callback;

interface henryInterface {

     void registerCallback(Callback callback);

     void unregisterCallback(Callback callback);

     void sendMessage(int type, String value);
}

Callback.aidl 内容如下

// Callback.aidl
package com.android.henryservice;

// Declare any non-default types here with import statements

interface Callback {
 oneway void onMessageReceived(int type, String value);
}

AIDL文件编写, 教程很多, 就不详细说明了, 需要注意的是, 由于要实现回调功能, 所以必须写一个回调接口 Callback , 另外AIDL文件中 oneway 关键字表明调用此函数不会阻塞当前线程, 调用端调用此函数会立即返回, 接收端收到函数调用是在Binder线程池中的某个线程中. 可以根据实际项目需求选择是否需要加 oneway 关键字.

AIDL只支持传输基本java类型数据, 要想传递自定义类, 类需要实现 Parcelable 接口, 另外, 如果传递基本类型数组, 需要指定 in out 关键字, 比如 void test(in byte[] input, out byte[] output) , 用 in 还是 out, 只需要记住: 数组如果作为参数, 通过调用端传给被调端, 则使用 in, 如果数组只是用来接受数据, 实际数据是由被调用端来填充的, 则使用 out。

文件写完后, 添加到编译文件Android.bp中
frameworks/base/Android.bp

java_defaults {
    name: "framework-defaults",
    installable: true,

    srcs: [
        // From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
        "core/java/**/*.java",
        "graphics/java/**/*.java",
        "location/java/**/*.java",
        "lowpan/java/**/*.java",
        "media/java/**/*.java",
        "media/mca/effect/java/**/*.java",
        "media/mca/filterfw/java/**/*.java",
        "media/mca/filterpacks/java/**/*.java",
        "drm/java/**/*.java",
        "opengl/java/**/*.java",
        "sax/java/**/*.java",
        "telecomm/java/**/*.java",
        "telephony/java/**/*.java",
        "wifi/java/**/*.java",
        "keystore/java/**/*.java",
        "rs/java/**/*.java",

        ":framework-javastream-protos",
        "core/java/com/android/henryservice/henryInterface.aidl",
        "core/java/com/android/henryservice/Callback.aidl",
        "core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
        //省略代码

编译代码, 编译前需执行 make update-api, 更新接口, 否则会报如下错误:
在这里插入图片描述
Android 10还有提示,Android 后期有些版本好像提示都不给了…

然后再编译代码,确保AIDL编写没有错误, 编译后会生成对应java文件, 服务端要实现对应接口.

make api-stubs-docs-update-current-api

在这里插入图片描述
看一下主要修改了哪个文件:
在这里插入图片描述


二、编写Manager类

可以看到Android API 中有很多Manager类, 这些类一般都是某个系统服务的客户端代理类, 其实如果不写Manager类, 只通过AIDL文件自动生成的类, 也可以完成功能, 但封装一下AIDL接口使用起来更方便, 我们测试用的Manager类为 henryManager, 代码如下:
frameworks/base/core/java/com/android/henryservice/henryManager.java

package com.android.henryservice;

import android.content.Context;
import android.os.RemoteException;
import android.util.Log;

public class henryManager {
    private static final String TAG = henryManager.class.getSimpleName();
    // 系统服务注册时使用的名字, 确保和已有的服务名字不冲突
    public static final String SERVICE = "henryservice";

    private final Context mContext;
    private final henryInterface mService;

    public henryManager(Context context, henryInterface service) {
        mContext = context;
        mService = service;
        Log.d(TAG, "henryManager init");
    }

    public void register(Callback callback) {
        try {
            mService.registerCallback(callback);
        } catch (RemoteException e) {
            Log.w(TAG, "remote exception happen");
            e.printStackTrace();
        }
    }

    public void unregister(Callback callback) {
        try {
            mService.unregisterCallback(callback);
        } catch (RemoteException e) {
            Log.w(TAG, "remote exception happen");
            e.printStackTrace();
        }
    }

    /**
     * Send event to SystemEventService.
     */
    public void sendEvent(int type, String value) {
        try {
            mService.sendMessage(type, value);
        } catch (RemoteException e) {
            Log.w(TAG, "remote exception happen");
            e.printStackTrace();
        }
    }
}

代码很简单, 就封装了下AIDL接口, 定义了系统服务注册时用的名字.

public henryManager(Context context, henryInterface service)

构造函数中的 henryInterface 参数在后面注册Manager时候会通过Binder相关接口获取.
编译代码, 确保没有错误, 下面编写系统服务.

三、编写系统服务

路径以及代码如下:
frameworks/base/services/core/java/com/android/server/henryservice/henryservice.java

package com.android.server.henryservice;

import android.content.Context;
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.android.henryservice.Callback;
import com.android.henryservice.henryInterface;


public class henryservice extends henryInterface.Stub {

    private static final String TAG = henryservice.class.getSimpleName();
    private RemoteCallbackList<Callback> mCallbackList = new RemoteCallbackList<>();

    private Context mContext;

    public henryservice(Context context) {
        mContext = context;
        Log.d(TAG, "henryservice init");
    }

    @Override
    public void registerCallback(Callback callback) {
        boolean result = mCallbackList.register(callback);
        Log.d(TAG, "register pid:" + Binder.getCallingPid()
                + " uid:" + Binder.getCallingUid() + " result:" + result);

    }

    @Override
    public void unregisterCallback(Callback callback) {
        boolean result = mCallbackList.unregister(callback);
        Log.d(TAG, "unregister pid:" + Binder.getCallingPid()
                + " uid:" + Binder.getCallingUid() + " result:" + result);

    }

    @Override
    public void sendMessage(int type, String value) throws RemoteException {
        sendEventToRemote(type, value + " remote");
    }

    public void sendEventToRemote(int type, String value) {
        int count = mCallbackList.getRegisteredCallbackCount();
        Log.d(TAG, "remote callback count:" + count);
        if (count > 0) {
            // 注意: 遍历过程如果存在多线程操作, 需要加锁, 不然可能会抛出异常
            final int size = mCallbackList.beginBroadcast();
            for (int i = 0; i < size; i++) {
                Callback cb = mCallbackList.getBroadcastItem(i);
                try {
                    if (cb != null) {
                        cb.onMessageReceived(type, value);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                    Log.d(TAG, "remote exception:" + e.getMessage());
                }
            }
            mCallbackList.finishBroadcast();
        }
    }

}

服务端继承自 henryInterface.Stub, 实现对应的三个方法即可, 需要注意的是, 由于有回调功能, 所以要把注册的 Callback 加到链表里面, 这里使用了 RemoteCallbackList, 之所以不能使用普通的 List 或者 Map, 原因是, 跨进程调用, App调用 registerCallback 和 unregisterCallback 时, 即便每次传递的都是同一个 Callback 对象, 但到服务端, 经过跨进程处理后, 就会生成不同的对象, 所以不能通过直接比较是否是同一个对象来判断是不是同一个客户端对象, Android中专门用来处理跨进程调用回调的类就是RemoteCallbackList, RemoteCallbackList 还能自动处理App端异常死亡情况, 这种情况会自动移除已经注册的回调.

RemoteCallbackList 使用非常简单, 注册和移除分别调用 register() 和 unregister() 即可, 遍历所有Callback 稍微麻烦一点, 代码参考上面的 sendEventToRemote() 方法.

可以看到测试用的的系统服务逻辑很简单, 注册和移除 Callback 调用 RemoteCallbackList 对应方法即可, sendMessage() 方法在App端调用的基础上, 在字符串后面加上 " remote" 后回调给App, 每个方法也加了log方便理解流程, 服务端代码就完成了。

四、注册系统服务

代码写好后, 要注册到SystemServer中, 所有系统服务都运行在名为 system_server 的进程中, 我们要把编写好的服务加进去, SystemServer中有很多服务, 把自己的系统服务加到最后面, 对应路径和代码如下:

frameworks/base/services/java/com/android/server/SystemServer.java

import com.android.server.henryservice.henryservice;
import com.android.henryservice.henryManager;


private void startOtherServices() {
    // 部分代码省略...
    // start SystemEventService
    try {
        ServiceManager.addService(henryManager.SERVICE,
                    new henryservice(context));
    } catch (Throwable e) {
        reportWtf("starting Henry Service", e);
    }
    // 部分代码省略...
}

通过 ServiceManager 将服务加到SystemServer中, 名字使用 henryManager.SERVICE, 后面获取服务会通过名字来获取. 此时, 如果直接编译运行, 开机后会出现如下错误:Selinux权限问题

05-08 10:46:04.900  1429  1429 D henryservice: henryservice init
05-08 10:46:04.901   475   475 E SELinux : avc:  denied  { add } for service=henryservice pid=1429 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0
05-08 10:46:04.901   475   475 E ServiceManager: add_service('henryservice',85) uid=1000 - PERMISSION DENIED

放到标题六去解决。

五、注册Manager

系统服务运行好了, 接下来就是App怎么获取的问题了, App获取系统服务, 我们也用通用接口:context.getSystemService()
在调用 getSystemService() 之前, 需要先注册, 代码如下:

frameworks/base/core/java/android/app/SystemServiceRegistry.java

import com.android.henryservice.henryInterface;
import com.android.henryservice.henryManager;


 final class SystemServiceRegistry {
             public MediaProjectionManager createService(ContextImpl ctx) {
                 return new MediaProjectionManager(ctx);
             }});
        registerService(henryManager.SERVICE, henryManager.class,
                new CachedServiceFetcher<henryManager>() {
            @Override
            public henryManager createService(ContextImpl ctx) {
            // 获取服务
            IBinder b = ServiceManager.getService(henryManager.SERVICE);
            // 转为 henryInterface
            henryInterface service = henryInterface.Stub.asInterface(b);
            return new henryManager(ctx, service);
            }});

         registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,
                 new CachedServiceFetcher<AppWidgetManager>() {

注册后, 如果你在App里面通过 getSystemService(SystemEventManager.SERVICE); 获取Manager并调用接口, 会发现又会出错, 又是Selinux权限问题:

SELinux : avc:  denied  { find } for service=henryservice pid=8189 uid=10141 scontext=u:r:untrusted_app:s0:c141,c256,c512,c768 tcontext=u:object_r:henryservice_service:s0 tclass=service_manager permissive=1

放到标题六去解决。

至此, 系统代码修改完成了, 编译系统刷机,通过adb shell setenforce 0 先将selinux设置为宽容模式:只报错但不影响使用。
整体修改的部分如下(除去SElinux权限):

		修改:     frameworks/base/Android.bp
        修改:     frameworks/base/api/current.txt
        修改:     frameworks/base/core/java/android/app/SystemServiceRegistry.java
        修改:     frameworks/base/services/java/com/android/server/SystemServer.java
        添加:frameworks/base/core/java/com/android/henryservice/													
													├── Callback.aidl
													├── henryInterface.aidl
													└── henryManager.java
		添加:frameworks/base/services/core/java/com/android/server/henryservice/
																	└── henryservice.java

六、Selinux权限解决

上面说了可以通过adb shell setenforce 0 先将selinux设置为宽容模式:只报错但不影响使用
但我们理应在源码中加入权限解决这类报错。

6.1 注册系统服务

首先定义类型, henryservice要和添加服务用的名字保持一致

system/sepolicy/prebuilts/api/29.0/private/service_contexts

wificond                                  u:object_r:wificond_service:s0
wifiaware                                 u:object_r:wifiaware_service:s0
wifirtt                                   u:object_r:rttmanager_service:s0
window                                    u:object_r:window_service:s0
henryservice                              u:object_r:henryservice_service:s0
*                                         u:object_r:default_android_service:s0

加入刚刚定义好的 henryservice_service类型, 表明它是系统服务
system/sepolicy/prebuilts/api/29.0/private/service.te

type statscompanion_service,        system_server_service, service_manager_type;
......
type henryservice_service,          system_api_service, system_server_service, service_manager_type;

此时编译,会报错提示如下:

文件 system/sepolicy/prebuilts/api/29.0/private/service.te 和 system/sepolicy/private/service.te 不同
文件 system/sepolicy/prebuilts/api/29.0/private/service_contexts 和 system/sepolicy/private/service_contexts 不同

将修改同步至system/sepolicy/private/service.te 和 system/sepolicy/private/service_contexts 即可

加入上面代码后, 编译刷机开机后, 服务就能正常运行了。

在这里插入图片描述
adb shell service list 检查一下:

trinket:/ # service list|grep henry
44      henryservice: [com.android.henryservice.henryInterface]

6.2 注册Manager

系统注册已经成功了,解决一下使用manager调用服务时报的selinux权限。
还需要再加一下权限,修改代码如下:

system/sepolicy/prebuilts/api/29.0/private/untrusted_app.te

allow untrusted_app henryservice_service:service_manager find;

编译报错,修改同步至 system/sepolicy/untrusted_app.te

文件 system/sepolicy/prebuilts/api/29.0/private/untrusted_app.te 和 system/sepolicy/private/untrusted_app.te 不同

七、App调用

文件拷贝和准备:
我们需要复制三个文件到App中, 两个AIDL文件, 一个Manager文件:

Callback.aidl
henryInterface.aidl
henryManager.java

.aidl文件放到App工程的aidl文件夹下面(没有这个文件夹的话要新建),
新版Android Studio建立aidl文件需要如下设置:

android {
    defaultConfig {
    ......
    }

    buildTypes {
	......
    }
    compileOptions {
    ......
    }
    buildFeatures{aidl true}
}

.java文件放到java目录下,
所有aidl文件和java文件, 在App工程中的包名需要和系统保持一致, 这三个文件App不能做任何修改, 除非系统源码中也做对应修改,最终App工程目录结构如下:
在这里插入图片描述
记得make一下

只需要看一下MainActivity的编写即可

package com.android.henryservice;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    henryManager eventManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        eventManager = (henryManager) getApplicationContext().getSystemService(henryManager.SERVICE);
        //获取服务
    }

    private Callback.Stub eventCallback = new Callback.Stub() {
        @Override
        public void onMessageReceived(int type, String value) throws RemoteException {
            Log.d("henrymanager", "type:" + type + " value:" + value);
        }

    };

    public void register(View view) {
        eventManager.register(eventCallback);
    }


    public void unregister(View view) {
        eventManager.unregister(eventCallback);
    }


    public void send(View view) {
        eventManager.sendEvent(666, "henry");
    }
}

这里Android Studio可能会报 getSystemService() 参数不是Context里面的某个服务的错误, 可以直接忽略, 不影响编译.

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/register"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:onClick="register"
        android:text="注册" />

    <Button
        android:id="@+id/unregister"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_below="@+id/register"
        android:layout_centerInParent="true"
        android:layout_marginTop="10dp"
        android:onClick="unregister"
        android:text="解绑" />

    <Button
        android:id="@+id/send"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_below="@+id/unregister"
        android:layout_centerInParent="true"
        android:layout_marginTop="10dp"
        android:onClick="send"
        android:text="发送" />

</RelativeLayout>

测试一下:
依次按下注册、发送、解绑、发送、发送

在这里插入图片描述

八、SDK接口限制

android9以上运行时,button按下后可能会报如下错误:
在这里插入图片描述

Caused by: java.lang.NoSuchMethodError: No virtual method register(Lcom/android/henryservice/Callback;)
V in class Lcom/android/henryservice/henryManager; 
or its super classes (declaration of 'com.android.henryservice.henryManager' 
appears in /system/framework/framework.jar!classes3.dex)                                                                                                    	at com.android.henryservice.MainActivity.register(MainActivity.java:32)

报错的原因涉及到SDK接口限制的问题,在 Android 9以后 ,针对非 SDK 接口进行了限制,默认是 blacklist 的,
在这里插入图片描述

通过命令

 m out/soong/hiddenapi/hiddenapi-flags.csv

可以生成包含所有非 SDK 接口及其对应的名单

vim out/soong/hiddenapi/hiddenapi-flags.csv

打开改文件,发现自己添加服务的部分接口都默认在blacklist清单里,导致自己上层调用报错,
在这里插入图片描述在这里插入图片描述

解决方法:
①临时设置为可以调用 adb shell settings put global hidden_api_policy 1
②编辑 greylist,将自己包名加入 greylist 中。

vim frameworks/base/config/hiddenapi-greylist-packages.txt 

最后一行增加,设置在清单里,之后可以正常调用该包下的接口。
在这里插入图片描述
m out/soong/hiddenapi/hiddenapi-flags.csv 再次编译发现自己包名的接口再也没有blacklist的了。
至于白名单、灰名单、黑名单具体概念,之后再详细研究吧。

九、系统服务的JNI部分代码

一般添加系统服务, 可能是为了调用驱动里面的代码, 所以一般要用JNI部分代码, 这里简单说下系统服务中已有的JNI代码, 可以直接在这基础上增加我们的功能.

JNI部分代码位置为:

frameworks/base/services/core/jni/

编译对应bp为:

frameworks/base/services/Android.bp
frameworks/base/services/core/jni/Android.bp

frameworks/base/services/Android.bp:

// merge all required services into one jar
// ============================================================
java_library {
    name: "services",
    installable: true,

    dex_preopt: {
        app_image: true,
        profile: "art-profile",
    },

    srcs: [
        "java/**/*.java",
    ],

    // The convention is to name each service module 'services.$(module_name)'
    static_libs: [
        "services.core",
        "services.accessibility",
        "services.appprediction",
        "services.appwidget",
        "services.autofill",
        "services.backup",
        "services.companion",
        "services.contentcapture",
        "services.contentsuggestions",
        "services.coverage",
        "services.devicepolicy",
        "services.midi",
        "services.net",
        "services.print",
        "services.restrictions",
        "services.startop",
        "services.systemcaptions",
        "services.usage",
        "services.usb",
        "services.voiceinteraction",
        "android.hidl.base-V1.0-java",
    ],

    libs: [
        "android.hidl.manager-V1.0-java",
    ],

    // Uncomment to enable output of certain warnings (deprecated, unchecked)
    //javacflags: ["-Xlint"],

}

// native library
// =============================================================

cc_library_shared {
    name: "libandroid_servers",
    defaults: ["libservices.core-libs"],
    whole_static_libs: ["libservices.core"],
}

cc_library_shared模块定义了一个共享的C/C++库模块,名称为libandroid_servers,默认依赖于libservices.core-libs,并且整体依赖于libservices.core。

此部分代码直接编译为 libandroid_servers 动态库, 在SystemServer进行加载:
frameworks/base/services/java/com/android/server/SystemServer.java

            // Prepare the main looper thread (this thread).
            android.os.Process.setThreadPriority(
                    android.os.Process.THREAD_PRIORITY_FOREGROUND);
            android.os.Process.setCanSelfBackground(false);
            Looper.prepareMainLooper();
            Looper.getMainLooper().setSlowLogThresholdMs(
                    SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);

            // Initialize native services.
            System.loadLibrary("android_servers");

如果需要添加JNI部分代码, 直接在 frameworks/base/services/core/jni/ 目录下增加对应文件,
在frameworks/base/services/core/jni/Android.bp中加入新增文件进行编译即可.
同时按照已有文件中JNI函数注册方式, 写好对应注册方法, 统一在frameworks/base/services/core/jni/onload.cpp 中动态注册函数.

参考链接:
Android11 —— 自定义添加一个System Services
Android java.lang.NoSuchMethodError:

  • 35
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值