今天看啥  ›  专栏  ›  土豆泥加盐

自定义一个SystemService

土豆泥加盐  · 掘金  ·  · 2021-06-08 18:25
阅读 120

自定义一个SystemService

通常我们通过如下代码来获取systemservice

        ActivityManager activityManager= (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        activityManager.getRunningAppProcesses();
复制代码

而getSystemService的相关实现是在ContextImpl:frameworks/base/core/java/android/app/ContextImpl.java,代码如下:

    @Override
    public Object getSystemService(String name) {
        if (vmIncorrectContextUseEnabled()) {
            // Check incorrect Context usage.
            if (isUiComponent(name) && !isSelfOrOuterUiContext()) {
                final String errorMessage = "Tried to access visual service "
                        + SystemServiceRegistry.getSystemServiceClassName(name)
                        + " from a non-visual Context:" + getOuterContext();
                final String message = "Visual services, such as WindowManager, WallpaperService "
                        + "or LayoutInflater should be accessed from Activity or other visual "
                        + "Context. Use an Activity or a Context created with "
                        + "Context#createWindowContext(int, Bundle), which are adjusted to "
                        + "the configuration and visual bounds of an area on screen.";
                final Exception exception = new IllegalAccessException(errorMessage);
                StrictMode.onIncorrectContextUsed(message, exception);
                Log.e(TAG, errorMessage + " " + message, exception);
            }
        }
        return SystemServiceRegistry.getSystemService(this, name);
    }
复制代码

可以看到最终是通过SystemServiceRegistry(frameworks/base/core/java/android/app/SystemServiceRegistry.java)的类来获取的,通过类名我们可以推断这里面一定有相关的注册订阅方法,进入后查看可以发现确实存在一个订阅函数:

    private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }
复制代码

并且在该类里,我们搜索对应的ActivityManager发现很多的Manager均在此内的静态代码块调用registerService进行了注册:

 static {
        ......省略......
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});
 }
        ......省略......
复制代码

可以看到我们最终get拿到的就是在这里订阅构造的ActivityManager,其实ActivityManager只是一个代理类,我们具体调用的实现还是依靠在ActivityManager里的Binder实现类Service提供的。 接下来我们自行实现一个SystemService,并且提供对应java包给应用进行调用。

编写AIDL文件

我们同样模仿ActivityManager将AIDL创建在frameworks/base/core/java/android/app/路径下即可 frameworks/base/core/java/android/app/yy/ITestService.aidl:

package android.app.yy;
/**
 * @hide
 * */
interface ITestService {
    String todo();
}
复制代码

添加servicename

接着我们去Context里补充一下获取Manager时用的静态变量,类似于Context.ACTIVITY_SERVICE,我们在getSystemService里传入的和registerService就是通过这个变量作为key,来进行映射查找。 frameworks/base/core/java/android/content/Context.java:

    public static final String TEST_SERVICE = "test";
    
    /**
    *另外Contex里还有个自定义注解@ServiceName,具体用在了Context.getSystemService的参数处做限制:
    *public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
    *所以我们为了一致性和规范性,也在@ServiceName的定义处把我们添加的Service补充进去:
    **/
    /** @hide */
    @StringDef(suffix = { "_SERVICE" }, value = {
            //......省略......
            TEST_SERVICE,
            ACTIVITY_SERVICE,
            //......省略......
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ServiceName {}
    
复制代码

实现Service逻辑

因为已经编写了AIDL,即在编译后会生成对应的Binder代理类,所以我们可以先去集成Stub把Service逻辑编写了: 到frameworks/base/services/core/java/com/android/server/路径下,可以看到有个am目录,ams即在该路径下实现的,我们模仿在此同级创建我们的service路径即可: frameworks/base/services/core/java/com/android/server/yy/TestService.java

package com.android.server.yy;
import android.content.Context;
import android.app.yy.ITestService;
public class TestService extends ITestService.Stub {
    private final Context mContext;

    public TestService(Context context) {
        super();
        mContext = context;
    }

    @Override
    public String todo() {
        return "调用todo成功";
    }
}
复制代码

注册Service

注意这里是注册Service而非注册Manager,所有的Service(Binder实现类)均是在SystemServer进程进行创建,并注册到ServiceManager进程的,Android中所有的Binder交互均是通过Binder驱动去ServiceManager找到对应Service进行通讯,frameworks/base/services/java/com/android/server/SystemServer.java:

    /**
     *  SystemServer的run里封装了三种启动不同类型service的函数,
     *  例如AMS这些就是在startBootstrapServices里创建并注册的,
     *  这里我们自定义的Service添加到startOtherServices里即可:
     */
    private void run() {
        // Start services.
        try {
            t.traceBegin("StartServices");
            startBootstrapServices(t);
            startCoreServices(t);
            startOtherServices(t);
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            t.traceEnd(); // StartServices
        }
    }
    private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
        //......省略......
        TestService testService=new TestService(context);
        ServiceManager.addService(Context.TEST_SERVICE,testService);
        //......省略......
    }
复制代码

实现Manager

接下来我们即可实现对应Manager,以提供给应用方使用,Manager实现在和AIDL同一路径, frameworks/base/core/java/android/app/yy/TestServiceManager.java

package android.app.yy;

import android.os.IBinder;
import android.os.ServiceManager;
import android.app.yy.ITestService;
import android.content.Context;
import android.os.RemoteException;
import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.Nullable;
import android.os.ServiceManager.ServiceNotFoundException;

public class TestServiceManager {
    private static TestServiceManager instance;
    private final ITestService mService;
    private Context mContext;

    /**
     * @hide
     */
    private TestServiceManager(ITestService service,Context context) {
        mService = service;
        mContext=context;
    }

    /**
     * @hide
     */
    @UnsupportedAppUsage
    public static TestServiceManager getInstance(@Nullable Context context) {
        synchronized (TestServiceManager.class) {
            if (instance == null) {
                try {
                    instance = new TestServiceManager(ITestService.Stub
                            .asInterface(ServiceManager.getService(Context.TEST_SERVICE)),context);
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
            }
            return instance;
        }
    }
    
    public @Nullable String todo() {
        try {
            return mService.todo();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
复制代码

可以看到实际上Manager只是Service的代理而已,而Service是通过ServiceManager.getService获取到的,通过的正是我们在SystemServer中addService传入的service_name。

Manager实现的注意事项

Android 高版本开始lint检测会对Manager有一定的限制,比如实现必须为单利,函数需要注解标识参数和返回值是否可为null,以及方法命名是否大小写(驼峰)语法规范等。一般是建议按照规范进行实现处理,如果是涉及到升级兼容的问题,可以根据编译日志提示,找到lint-baseline.txt编写规则进行忽略,具体的编写规则,会在报错后输出到api_lint_baseline.txt

注册Manager

接下来我们只需要像最开始分析的那样把Manager在SystemServiceRegistry内注册,用户即可通过getSystemService获取到Manager对象进行调用了。frameworks/base/core/java/android/app/SystemServiceRegistry.java:

    static {
        registerService(Context.TEST_SERVICE, TestServiceManager.class,
                new CachedServiceFetcher<TestServiceManager>() {
                    @Override
                    public TestServiceManager createService(ContextImpl ctx) {
                        return TestServiceManager.getInstance(ctx);
                    }
                });
    }
复制代码

补充current.txt

最后因为规定修改了framework/base下的api需要更新current.txt,可以通过 make update-api会自动在framewrok/base/api下的current.txt中生成

SE权限

最后需要添加一下service的se权限,否则无法访问,一般是在device路径下找对应的te文件进行修改,这里路径每个项目不太一样,此处以android源码下mtk路径为例子: device/mediatek/wembley-sepolicy/plat_public/service.te

# ==============================================
# MTK Policy Rule
# ==============================================

# System Server Services

# Other Services
type nvram_agent_service, service_manager_type;
#定义我们的service
type test_service,   app_api_service,system_server_service,service_manager_type; 
复制代码

device/mediatek/wembley-sepolicy/plat_private/service_contexts

# ==============================================
# MTK Policy Rule
# ==============================================

# System Server Services

# Other Services
NvRAMAgent                              u:object_r:nvram_agent_service:s0
memory_dumper                           u:object_r:mediaserver_service:s0
imsa                                    u:object_r:radio_service:s0
mtkIms                                  u:object_r:radio_service:s0
GbaService                              u:object_r:radio_service:s0
#定义我们的service
test                              u:object_r:test_service:s0
复制代码

使用

因为是自己创建的Service,SDK并没有该接口暴露,所以为了使用和编译,可以自己创建一个对应路径的Manager类编译成jar包提供给用户,例如: 创建android.app.yy路径,创建TestServiceManager.java,里面添加空方法todo,然后编译成jar包给用户。 用户使用:

   /**
         * 因为Lint检测,getSystemService传参会要求传Context的静态变量,如果想要通过传入Class,则需要高版本适配
         * 所以可以通过@SuppressLint("WrongConstant")注解忽略
         */
        @SuppressLint("WrongConstant")TestServiceManager manager= (TestServiceManager) getSystemService("test");
        String ret=manager.todo();
复制代码



原文地址:访问原文地址
快照地址: 访问文章快照