Android定制实战:让指定应用在原生设置“所有应用“列表中隐身

本文介绍在Android原生Settings的'所有应用'列表中,通过修改SettingsLib中的ApplicationFilter逻辑,在数据过滤链路最前端无条件排除指定包名,实现应用界面隐身但功能完全可用的技术方案。重点分析了过滤器切入位置、静态数组定义、安全性保障(不修改组件状态、不影响运行)、副作用评估(丢失详情页入口)及验证方法,适用于系统定制场景。

场景:原生 Settings 的"所有应用"页面,勾选"显示系统应用"后,需要让 com.example.dmapp 这个系统应用不出现在列表里——但完全不影响它的正常运行。


一、需求分析

先明确需求的三个关键点:

  1. 只改"显示":应用本体必须正常运行,不能 disable、不能改 manifest;
  2. 入口是原生 Settings:页面是 packages/apps/Settings 里的"所有应用"列表(ManageApplications),不是三方桌面;
  3. 必须连"显示系统应用"模式下也隐藏:很多系统的隐藏方案(比如 config_hideWhenDisabled_packageNames)只对"已禁用"的应用生效,一个处于启用状态的系统应用照样会露出来。

Android 原生 Settings 提供的"应用图标隐藏"手段主要有两类:

  • <activity android:excludeFromRecents> / launcher 图标隐藏:只影响桌面图标,不影响设置列表;
  • Settings 内部的过滤数组 config_hideWhenDisabled_packageNames:把包名加进这个数组后,"已禁用"的应用会被隐藏——但启用中的应用仍然显示,且这是通用资源数组,会波及所有用到它的过滤场景。

都不满足"强制隐藏 + 不影响使用",所以需要从 Settings 列表的数据过滤链路下手。

二、定位代码:应用列表的过滤链路

原生设置"所有应用"页面对应:

packages/apps/Settings/src/com/android/settings/applications/manageapplications/ManageApplications.java

ManageApplications 内部类 ApplicationsAdapter 的 rebuild() 方法是列表数据的组装入口,关键代码:

filterObj = mAppFilter.getFilter();
if (mCompositeFilter != null) {
    filterObj = new CompoundFilter(filterObj, mCompositeFilter);
}
if (!mManageApplications.mShowSystem) {
    // 未勾选"显示系统应用"时,只显示已下载/带启动器的应用
    filterObj = new CompoundFilter(filterObj,
            ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
}
// ...排序逻辑...

final AppFilter finalFilterObj = new CompoundFilter(filterObj,
        ApplicationsState.FILTER_NOT_HIDE);   // ★ 关键:最后统一套一层
mSession.rebuild(finalFilterObj, comparatorObj, false);

注意到最关键的一行:无论列表类型是什么、无论用户是否勾选了"显示系统应用",最终都会再包一层 ApplicationsState.FILTER_NOT_HIDE。这就是我们的切入点——在这个过滤器里做无条件排除,即可覆盖所有显示分支。

追进 SettingsLib:

frameworks/base/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java

FILTER_NOT_HIDE 的原始实现(Android 11/12 附近版本):

public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
    private String[] mHidePackageNames;

    @Override
    public void init(Context context) {
        mHidePackageNames = context.getResources()
                .getStringArray(R.array.config_hideWhenDisabled_packageNames);
    }

    @Override
    public void init() {
    }

    @Override
    public boolean filterApp(AppEntry entry) {
        if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
            if (!entry.info.enabled) {
                return false;   // 数组里的包:已禁用 → 不显示
            } else if (entry.info.enabledSetting ==
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                return false;   // 数组里的包:禁用直到使用 → 不显示
            }
        }
        return true;
    }
};

语义是"隐藏被禁用的指定包"。我们要在这个过滤器上叠加"无论启用与否,指定包一律不显示"。

三、修改方案

1. 定义"强制隐藏"包名数组

/** Custom: packages always hidden from the Settings application list. */
private static final String[] HIDE_ALWAYS_PACKAGES = {
        "com.example.dmapp",
};

用静态数组而不是散落的字符串硬编码,后续如果要隐藏更多包(常见于定制项目),直接往数组里加一行即可。

2. 在 filterApp() 最前面做无条件排除

@Override
public boolean filterApp(AppEntry entry) {
    // Custom: always hide these packages in Settings app list,
    // including the "show system apps" list.
    if (ArrayUtils.contains(HIDE_ALWAYS_PACKAGES, entry.info.packageName)) {
        return false;
    }
    if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
        if (!entry.info.enabled) {
            return false;
        } else if (entry.info.enabledSetting ==
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
            return false;
        }
    }
    return true;
}

filterApp 返回 false 的含义仅仅是"这一条不进列表",它只是 Settings 渲染列表时的数据筛选,与 PackageManager.setApplicationEnabledSetting() 之类的系统状态操作毫无关系。

3. 完整 diff

 frameworks/base/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java

+    /** Custom: packages always hidden from the Settings application list. */
+    private static final String[] HIDE_ALWAYS_PACKAGES = {
+            "com.example.dmapp",
+    };
+
     public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
         ...
         @Override
         public boolean filterApp(AppEntry entry) {
+            // Custom: always hide these packages in Settings app list,
+            // including the "show system apps" list.
+            if (ArrayUtils.contains(HIDE_ALWAYS_PACKAGES, entry.info.packageName)) {
+                return false;
+            }
             if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
                 ...

四、为什么这样改是安全的

这是这类需求里最容易被质疑的点,值得展开讲清楚:

  1. 只影响列表渲染:FILTER_NOT_HIDE 是 Settings UI 的数据过滤器,作用域仅限"所有应用/系统应用"这一页面的条目显示。应用被过滤掉,不代表应用被禁用。
  2. 不触碰系统状态:没有修改 ApplicationInfo.enabled、没有调用 PackageManager 的组件开关接口,com.example.dmapp 照常运行、照常能被其它模块(设备维护 App 等系统模块)隐式/显式拉起。
  3. 覆盖所有显示分支:因为 finalFilterObj = new CompoundFilter(filterObj, FILTER_NOT_HIDE) 是无条件套用的,“仅已下载”“仅已启用”"显示系统应用"等各种过滤模式、以及列表搜索框,都会命中这层过滤,不会漏。
  4. 影响面收敛在一个文件:没有动资源数组 config_hideWhenDisabled_packageNames,避免波及其它同样读取该数组的逻辑(如某些车机/TV 定制分支用它控制别处的显示)。

唯一的副作用(需要评估)

应用被隐藏后,用户无法再从"设置 → 应用"进入它的应用详情页(停用/权限/存储清理等操作入口)。如果业务流程中有地方依赖"跳转到该应用的应用详情页",需要换一种隐藏条件(例如仅在 mShowSystem == true 的分支隐藏,或在详情页入口处单独放行)。本项目 dmapp 无此类入口,所以采用无条件隐藏。

五、验证与编译

  1. 增量编译(SettingsLib 是 framework 侧库,Settings 通过它取数据):
source build/envsetup.sh
lunch <product>-userdebug
make Settings SettingsLib -j16
  1. 刷入 Settings APK(或整编出升级包)后验证:
    • 设置 → 应用 → 所有应用;
    • 点右上角菜单 → 显示系统应用:仍然搜不到;
    • 设备维护 App → 应用程序:功能正常,dmapp 相关能力不受影响;
    • adb shell pm list packages | grep dmapp:包仍在,dumpsys package com.example.dmapp 状态为 enabled。

六、延伸:Android 上"隐藏应用"的几种手段对比

手段作用范围是否影响运行适用场景
桌面图标隐藏(category/drawer 排除)仅 Launcher否只想桌面看不到
config_hideWhenDisabled_packageNamesSettings 列表(仅禁用态)否隐藏已停用的包
PackageManager.setApplicationEnabledSetting(DISABLED_USER)全系统是,等于禁用不可用,会破坏功能
本文:在 FILTER_NOT_HIDE 中加白名单式排除Settings 应用列表(所有分支)否隐藏但要求应用继续可用
系统级 packages.xml hidden 标志全系统不可见视实现通常用于 A/B 测试残余包

七、总结

  • 定制"列表里看不到"需求时,先找到 UI 数据链路的统一收口过滤器,比在各个显示分支上打补丁更可靠;
  • ManageApplications.rebuild() 末尾无条件叠加的 FILTER_NOT_HIDE 就是这个收口;
  • 过滤器只管"显示与否",不会影响应用的运行状态——这是"隐藏但可用"类需求的标准做法;
  • 隐藏前务必确认没有业务入口依赖"从设置列表进入该应用详情页"。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值