场景:原生 Settings 的"所有应用"页面,勾选"显示系统应用"后,需要让
com.example.dmapp这个系统应用不出现在列表里——但完全不影响它的正常运行。
一、需求分析
先明确需求的三个关键点:
- 只改"显示":应用本体必须正常运行,不能 disable、不能改 manifest;
- 入口是原生 Settings:页面是
packages/apps/Settings里的"所有应用"列表(ManageApplications),不是三方桌面; - 必须连"显示系统应用"模式下也隐藏:很多系统的隐藏方案(比如
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)) {
...
四、为什么这样改是安全的
这是这类需求里最容易被质疑的点,值得展开讲清楚:
- 只影响列表渲染:
FILTER_NOT_HIDE是 Settings UI 的数据过滤器,作用域仅限"所有应用/系统应用"这一页面的条目显示。应用被过滤掉,不代表应用被禁用。 - 不触碰系统状态:没有修改
ApplicationInfo.enabled、没有调用PackageManager的组件开关接口,com.example.dmapp照常运行、照常能被其它模块(设备维护 App 等系统模块)隐式/显式拉起。 - 覆盖所有显示分支:因为
finalFilterObj = new CompoundFilter(filterObj, FILTER_NOT_HIDE)是无条件套用的,“仅已下载”“仅已启用”"显示系统应用"等各种过滤模式、以及列表搜索框,都会命中这层过滤,不会漏。 - 影响面收敛在一个文件:没有动资源数组
config_hideWhenDisabled_packageNames,避免波及其它同样读取该数组的逻辑(如某些车机/TV 定制分支用它控制别处的显示)。
唯一的副作用(需要评估)
应用被隐藏后,用户无法再从"设置 → 应用"进入它的应用详情页(停用/权限/存储清理等操作入口)。如果业务流程中有地方依赖"跳转到该应用的应用详情页",需要换一种隐藏条件(例如仅在 mShowSystem == true 的分支隐藏,或在详情页入口处单独放行)。本项目 dmapp 无此类入口,所以采用无条件隐藏。
五、验证与编译
- 增量编译(SettingsLib 是 framework 侧库,Settings 通过它取数据):
source build/envsetup.sh
lunch <product>-userdebug
make Settings SettingsLib -j16
- 刷入 Settings APK(或整编出升级包)后验证:
- 设置 → 应用 → 所有应用;
- 点右上角菜单 → 显示系统应用:仍然搜不到;
- 设备维护 App → 应用程序:功能正常,
dmapp相关能力不受影响; adb shell pm list packages | grep dmapp:包仍在,dumpsys package com.example.dmapp状态为 enabled。
六、延伸:Android 上"隐藏应用"的几种手段对比
| 手段 | 作用范围 | 是否影响运行 | 适用场景 |
|---|---|---|---|
| 桌面图标隐藏(category/drawer 排除) | 仅 Launcher | 否 | 只想桌面看不到 |
config_hideWhenDisabled_packageNames | Settings 列表(仅禁用态) | 否 | 隐藏已停用的包 |
PackageManager.setApplicationEnabledSetting(DISABLED_USER) | 全系统 | 是,等于禁用 | 不可用,会破坏功能 |
本文:在 FILTER_NOT_HIDE 中加白名单式排除 | Settings 应用列表(所有分支) | 否 | 隐藏但要求应用继续可用 |
系统级 packages.xml hidden 标志 | 全系统不可见 | 视实现 | 通常用于 A/B 测试残余包 |
七、总结
- 定制"列表里看不到"需求时,先找到 UI 数据链路的统一收口过滤器,比在各个显示分支上打补丁更可靠;
ManageApplications.rebuild()末尾无条件叠加的FILTER_NOT_HIDE就是这个收口;- 过滤器只管"显示与否",不会影响应用的运行状态——这是"隐藏但可用"类需求的标准做法;
- 隐藏前务必确认没有业务入口依赖"从设置列表进入该应用详情页"。
本文介绍在Android原生Settings的'所有应用'列表中,通过修改SettingsLib中的ApplicationFilter逻辑,在数据过滤链路最前端无条件排除指定包名,实现应用界面隐身但功能完全可用的技术方案。重点分析了过滤器切入位置、静态数组定义、安全性保障(不修改组件状态、不影响运行)、副作用评估(丢失详情页入口)及验证方法,适用于系统定制场景。

361

被折叠的 条评论
为什么被折叠?



