[Android][Framework]裁剪SystemServer服務以及關閉SystemFeature

来源:https://www.cnblogs.com/rossoneri/archive/2018/08/30/9562317.html
-Advertisement-
Play Games

有時候因為系統應用場景不同,需要的功能也不一樣。所以就會有很多不需要的服務或者進程存在,因此將這些不需要的東西裁減掉也是必須面對的問題。本文就是對裁剪服務部分的一些簡單整理。 ...


本文鏈接 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,紅外線等等很多服務裁剪掉,以適應不同應用場景下的精簡系統。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 一、概述 二、MySQL安裝 三、安裝成功驗證 四、NavicatforMySQL下載及使用 一、MySQL下載 MySQL版本:5.7.17 下載地址:https://dev.mysql.com/downloads/mysql/ 客戶端工具:NavicatforMySQL 綠色版下載地址:h ...
  • 登錄資料庫後,選擇資料庫時發現以下提示, mysql> use testReading table information for completion of table and column namesYou can turn off this feature to get a quicker s ...
  • 其實簡單的來說,大數據就是通過分析和挖掘全量的非抽樣的數據輔助決策。 大數據可以實現的應用可以概括為兩個方向,一個是精準化定製,第二個是預測。比如像通過搜索引擎搜索同樣的內容,每個人的結果卻是大不相同的。再比如精準營銷、百度的推廣、淘寶的喜歡推薦,或者你到了一個地方,自動給你推薦周邊的消費設施等等。 ...
  • centos7下mysql5.7安裝、修改字元集、設置遠程訪問連接 ...
  • replace語法: REPLACE(char,search_string,[replacement_string]) 在replace中,每個search_String 都會被replacement_string替換掉,而replacement_string可以為null或者空字元串,search ...
  • rank() over,dense_rank() over,row_number() over的區別 1.rank() over:查出指定條件後的進行排名。特點是,加入是對學生排名,使用這個函數,成績相同的兩名是併列,下一位同學空出所占的名次。 select name,subject,score,r ...
  • 1、查詢是否鎖表 show OPEN TABLES where In_use > 0; 查詢到相對應的進程 然後 kill id 2、查詢進程 show processlist 補充: 查看正在鎖的事務 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 查 ...
  • 大數據名詞(1) -Shuffle 大數據名詞(1) -Shuffle 大數據名詞(1) -Shuffle 大數據名詞(1) -Shuffle Shuffle過程是MapReduce的核心,也被稱為奇跡發生的地方。要想理解MapReduce, Shuffle是必須要瞭解的。我看過很多相關的資料,但每 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...