• [Android][Framework]裁剪SystemServer服务以及关闭SystemFeature


    本文链接 http://wossoneri.github.io/2018/08/30/[Android][Framework]crop-SystemServer-and-SystemFeature/

    SystemServer服务裁剪

    有些系统,因为应用场景的不同,需要的服务也不一样。比如Android Things,为了应对IOT的应用场景,它就裁剪掉了很多服务。下面介绍一下裁剪服务的方法。

    关于服务,要提一下SystemServer,具体介绍见另一篇文章:http://wossoneri.github.io。SystemServer启动了系统的核心服务,除此之外,SystemServer还启动了很多其他服务,具体是在startOtherServices()方法中。我们要裁剪不需要的服务就可以从这里入手。

    比如要关闭打印机服务:

    可以直接把相关启动服务的代码注释掉:

    //mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
    

    当然这样修改后,以后如果要再打开,还需要再次修改SystemServer,然后编译jar包,push到设备使其生效。

    所以建议使用下面的改法:

    首先定义boolean变量,从全局属性读取配置,

    boolean disablePrinter = SystemProperties.getBoolean("config.disable_printer", false);
    

    然后在启动服务的代码段添加对这个属性的判断:

    if (!disablePrinter && mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
        mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
    }
    

    之后在MakeFile增加自定义的系统权限:

    PRODUCT_PROPERTY_OVERRIDES += 
        config.disable_printer=true
    

    以后如果要打开这个服务,就把true变成false就可以了。

    如果要调试,从修改设备的 /system/build.prop 然后重启即可。非常方便有木有!

    如果要修改,删掉out目录下的build.prop,重新编译system(或者直接修改build.prop然后make snod),烧录启动系统之后,运行如下命令进行验证:

    service check printer
    

    这样就不会再启动不需要的服务了。

    裁剪服务引发的问题

    服务不是你不让它Start就完事儿了,系统那么大,总有一些地方会获取服务对象做一些调用处理。比如我们刚裁减掉了打印机服务,然后打开Settings就遇到了crash:

    E AndroidRuntime: FATAL EXCEPTION: main
    E AndroidRuntime: Process: com.android.settings, PID: 3496
    E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.settings/com.android.settings.Settings}: java.lang.NullPointerException: Attempt to invoke interface method 'java.util.List android.print.IPrintManager.getPrintServices(int, int)' on a null object reference
    E AndroidRuntime: 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
    E AndroidRuntime: 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
    E AndroidRuntime: 	at android.app.ActivityThread.-wrap12(ActivityThread.java)
    E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
    E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:102)
    E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:154)
    E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6119)
    E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
    E AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:900)
    E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:790)
    E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.util.List android.print.IPrintManager.getPrintServices(int, int)' on a null object reference
    E AndroidRuntime: 	at android.print.PrintManager.getPrintServices(PrintManager.java:635)
    E AndroidRuntime: 	at android.print.PrintServicesLoader.onStartLoading(PrintServicesLoader.java:88)
    E AndroidRuntime: 	at android.content.Loader.startLoading(Loader.java:290)
    E AndroidRuntime: 	at android.app.LoaderManagerImpl$LoaderInfo.start(LoaderManager.java:283)
    E AndroidRuntime: 	at android.app.LoaderManagerImpl.installLoader(LoaderManager.java:579)
    E AndroidRuntime: 	at android.app.LoaderManagerImpl.createAndInstallLoader(LoaderManager.java:566)
    E AndroidRuntime: 	at android.app.LoaderManagerImpl.initLoader(LoaderManager.java:619)
    E AndroidRuntime: 	at com.android.settings.search.DynamicIndexableContentMonitor.register(DynamicIndexableContentMonitor.java:136)
    E AndroidRuntime: 	at com.android.settings.SettingsActivity.onStart(SettingsActivity.java:868)
    E AndroidRuntime: 	at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1249)
    E AndroidRuntime: 	at android.app.Activity.performStart(Activity.java:6737)
    E AndroidRuntime: 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2628)
    E AndroidRuntime: 	... 9 more
    

    通过堆栈信息,可以知道PrintManager.getPrintServices出现了空指针。这里也不用看代码就能猜到,因为我们开机没有启动打印服务,所以肯定get不到这个服务的。

    然后考虑修改方案,增加非空保护是不是就可以了?Naive!我们的目的是裁剪打印服务,所以我们的修改点并不在这个服务本身,而是删除所以调用这个服务的地方。

    所以堆栈显示的PrintManager,PrintServicesLoader什么的统统不要改,我们要看代码结构,看看是怎么调用进来的。通过阅读代码,了解到系统里有很多Loader类型的对象,其中一个子类就是PrintServicesLoader。然后这些Loader是由LoaderManager管理启动的。而LoaderManager在DynamicIndexableContentMonitor.java出现过一次初始化操作。

    看下DynamicIndexableContentMonitor.java代码:

    public void register(Activity activity, int loaderId) {
        ...
    	boolean hasFeaturePrinting = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
        ...
    	if (hasFeaturePrinting) {
    		activity.getLoaderManager().initLoader(loaderId, null, this);
    	}
        ...
    

    有木有发现一个熟悉的代码?

    对,代码里再次出现了一个有关SystemFeature的判断!上一次出现时SystemServer启动服务前也做了相同的判断。

    所以要裁剪掉打印机服务,我们只需要将FEATURE_PRINTING关闭即可。

    通过修改SystemFeature判断后,在SystemServer里面的裁剪代码就可以不再添加了。但是有些服务的裁剪Android并没有添加系统特性的处理,所以还是建议使用我的方法进行裁剪。

    SystemFeature加载流程

    先看一看FEATURE_PRINTING

    PackageManager.java

    /**
     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
     * The device supports printing.
     */
    @SdkConstant(SdkConstantType.FEATURE)
    public static final String FEATURE_PRINTING = "android.software.print";
    
    /**
     * Get a list of features that are available on the
     * system.
     *
     * @return An array of FeatureInfo classes describing the features
     * that are available on the system, or null if there are none(!!).
     */
    public abstract FeatureInfo[] getSystemAvailableFeatures();
    
    /**
     * Check whether the given feature name is one of the available features as
     * returned by {@link #getSystemAvailableFeatures()}. This tests for the
     * presence of <em>any</em> version of the given feature name; use
     * {@link #hasSystemFeature(String, int)} to check for a minimum version.
     *
     * @return Returns true if the devices supports the feature, else false.
     */
    public abstract boolean hasSystemFeature(String name);
    
    /**
     * Check whether the given feature name and version is one of the available
     * features as returned by {@link #getSystemAvailableFeatures()}. Since
     * features are defined to always be backwards compatible, this returns true
     * if the available feature version is greater than or equal to the
     * requested version.
     *
     * @return Returns true if the devices supports the feature, else false.
     */
    public abstract boolean hasSystemFeature(String name, int version);
    

    都是抽象方法,我们去PMS查找对应的实现

    PackageManagerService.java

    public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() {
        synchronized (mPackages) {
            final ArrayList<FeatureInfo> res = new ArrayList<>(mAvailableFeatures.values());
    
            final FeatureInfo fi = new FeatureInfo();
            fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version",
                                                        FeatureInfo.GL_ES_VERSION_UNDEFINED);
            res.add(fi);
    
            return new ParceledListSlice<>(res);
        }
    }
    
    @Override
    public boolean hasSystemFeature(String name, int version) {
        synchronized (mPackages) {
            final FeatureInfo feat = mAvailableFeatures.get(name);
            if (feat == null) {
                return false;
            } else {
                return feat.version >= version;
            }
        }
    }
    

    这里的逻辑都是通过mAvailableFeatures得到所有的feature,查找该成员变量的相关代码

    final ArrayMap<String, FeatureInfo> mAvailableFeatures;
    
    SystemConfig systemConfig = SystemConfig.getInstance();
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();
    

    了解到,首先获取一个SystemConfig的单例,然后通过getAvailableFeatures方法获取可用的feature。

    SystemConfig.java

    // These are the features this devices supports that were read from the
    // system configuration files.
    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
    
    public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
        return mAvailableFeatures;
    }
    
    private void addFeature(String name, int version) {
        FeatureInfo fi = mAvailableFeatures.get(name);
        if (fi == null) {
            fi = new FeatureInfo();
            fi.name = name;
            fi.version = version;
            mAvailableFeatures.put(name, fi);
        } else {
            fi.version = Math.max(fi.version, version);
        }
    }
    
    private void removeFeature(String name) {
        if (mAvailableFeatures.remove(name) != null) {
            Slog.d(TAG, "Removed unavailable feature " + name);
        }
    }
    

    根据mAvailableFeatures的注释,设备支持的feature是从配置文件里读取出来的。调用读取配置文件的地方是:

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        // Allow ODM to customize system configs around libs, features and apps
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
            Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
            Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
        // Only allow OEM to customize features
        readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    }
    

    到此就很明白了,它是读取了几个目录:

    • /system/etc/permission
    • /system/etc/sysconfig
    • /oem/etc/permission
    • /oem/etc/sysconfig
    • /odm/etc/permission
    • /odm/etc/sysconfig

    然后遍历xml文件,进行解析处理。SystemFeature就是解析的Feature标签。

    最后再总结一下加载流程:

    屏蔽SystemFeature

    知道原理就好做了,在系统扫描的几个目录中使用grep命令查找控制打印机的字串,找到:

    /system/etc/permission/handheld_core_hardware.xml

        <!-- basic system services -->
        <feature name="android.software.app_widgets" />
        <feature name="android.software.connectionservice" />
        <feature name="android.software.voice_recognizers" notLowRam="true" />
        <feature name="android.software.backup" />
        <feature name="android.software.home_screen" />
        <feature name="android.software.input_methods" />
        <feature name="android.software.print" />   <------这个就是打印特性
    

    将其注释掉就可以在手机进行测试了。

    但是,我们还需要修改源码,保证以后编译系统这个值都是被屏蔽的。

    查找MakeFile,找到如下:

    PRODUCT_COPY_FILES :=  frameworks/native/data/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml
    

    这个文件在源码中的位置是frameworks/native/data/etc/。找到该源码文件,将不要的Feature注释掉,然后重新编译源码,启动系统,一切正常!打印机相关的服务彻底被屏蔽掉了,系统启动速度,资源消耗又变小了一点点。嗯,是很小的一点点,我们还可以把VR,红外线等等很多服务裁剪掉,以适应不同应用场景下的精简系统。

  • 相关阅读:
    Tomcat 中会话超时的相关配置
    Oracle10g任务调度创建步骤
    Oracle的三种高可用集群方案
    软/硬件负载均衡产品 你知多少?
    Nginx、LVS及HAProxy负载均衡软件的优缺点详解
    java.sql.SQLException: ORA-00604: 递归 SQL 级别 1 出现错误
    TortoiseSVN客户端重新设置用户名和密码
    Linux下oracle数据库启动和关闭操作
    Linux下使用ps命令来查看Oracle相关的进程
    【Kill】两条Linux命令彻底杀死Oracle
  • 原文地址:https://www.cnblogs.com/rossoneri/p/9562317.html
Copyright © 2020-2023  润新知