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

2024-06-04 6873阅读

目录

  • 前言
  • 一、编写AIDL文件
  • 二、编写Manager类
  • 三、编写系统服务
  • 四、注册系统服务
  • 五、注册Manager
  • 六、Selinux权限解决
    • 6.1 注册系统服务
    • 6.2 注册Manager
    • 七、App调用
    • 八、SDK接口限制
    • 九、系统服务的JNI部分代码

      前言

      系统服务是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 添加系统服务的完整流程SystemService(史上最全) 第1张

      Android 10还有提示,Android 后期有些版本好像提示都不给了…

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

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

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

      看一下主要修改了哪个文件:

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


      二、编写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 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  
      

      服务端继承自 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() {
                  @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() {
      

      注册后, 如果你在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 即可

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

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

      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工程目录结构如下:

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

      记得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里面的某个服务的错误, 可以直接忽略, 不影响编译.

      布局文件:

      
          
          
          
      
      

      测试一下:

      依次按下注册、发送、解绑、发送、发送

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

      八、SDK接口限制

      android9以上运行时,button按下后可能会报如下错误:

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

      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 的,

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

      通过命令

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

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

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

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

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

      解决方法:

      ①临时设置为可以调用 adb shell settings put global hidden_api_policy 1

      ②编辑 greylist,将自己包名加入 greylist 中。

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

      最后一行增加,设置在清单里,之后可以正常调用该包下的接口。

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

      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:


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]