在 Android 系统中,SystemUI 是负责状态栏、通知栏、快捷开关、锁屏等核心系统 UI 的关键组件。传统上,这部分能力完全由系统内置代码实现,扩展和定制成本较高。
为了解耦系统 UI 与定制逻辑,Android 提供了 Plugin(插件)机制,允许在不修改 SystemUI 主体代码的情况下,对系统 UI 行为进行动态扩展。这也是车机、定制 ROM、厂商 UI 中非常重要的一种扩展手段。
一、什么是 SystemUI 插件?
简单说,SystemUI 插件就是:
一个运行在独立 Context / ClassLoader 中,但被 SystemUI 动态加载并调用的扩展模块
它具备几个关键特征:
动态加载:通过反射 + ClassLoader 加载 APK
接口驱动:通过接口(Plugin Interface)与 SystemUI 通信
资源隔离:插件拥有独立的资源体系(pluginContext)
生命周期托管:由 SystemUI 控制创建、销毁
你可以把它类比为:
👉 类似 Android 的"系统级热插拔组件"
二、为什么需要插件机制?
从工程角度看,插件机制解决的是三个核心问题:
1️⃣ 解耦(Decoupling)
SystemUI 主体代码不需要知道具体实现细节,只依赖接口:
java
@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
public interface OverlayPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
int VERSION = 4;
/**
* Setup overlay plugin
*/
void setup(View statusBar, View navBar);
}
插件实现在插件apk中:
java
class MyOverlayPlugin : OverlayPlugin {
override fun setup(View statusBar, View navBar) {
// 自定义逻辑
}
}
<service
android:name=".MyOverlayPlugin"
android:exported="false"
adroid:label="@string/plugin_label"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
</intent-filter>
</service>
2️⃣ 动态扩展(Extensibility)
无需修改系统源码即可扩展功能,例如:
自定义状态栏 UI
替换 QS(Quick Settings)面板
插入悬浮层(Overlay)
修改锁屏行为
3️⃣ 调试与实验(Experimentation)
插件机制天然适合:
A/B 实验
功能灰度发布
快速验证 UI/交互方案
SystemUI 如何加载插件
PluginListener PluginInstance PackageManager PluginActionManager PluginActionManager.Factory PluginManagerImpl SystemUI PluginListener PluginInstance PackageManager PluginActionManager PluginActionManager.Factory PluginManagerImpl SystemUI 存储action到mPluginPrefs loop [遍历每个插件服务] 监听包安装/更新/卸载事件 addPluginListener(action, listener, cls, allowMultiple) create(action, listener, cls, allowMultiple, isDebuggable) 返回 PluginActionManager<T> 实例 loadAll() 在后台线程执行 queryAll() 检查并断开现有插件实例 清空 mPluginInstances 列表 queryIntentServices(intent) 查询插件 返回 ResolveInfo 列表 验证插件权限和有效性 create(context, appInfo, component, pluginClass, listener) 返回 PluginInstance 添加到 mPluginInstances 列表 在前台线程执行 onPluginConnected() onCreate() onPluginConnected(plugin) 同步块 - 将listener和actionManager存入mPluginMap startListening() 注册包变更广播接收器
资源及异常隔离
- systemuiapk 通过独立的classloader来加载,
实现与systemui的资源隔离,
java
private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
return new PluginManagerImpl.ClassLoaderFilter(
baseClassLoader,
"androidx.constraintlayout.widget",
"com.android.systemui.common",
"com.android.systemui.log",
"com.android.systemui.plugin");
}
只有这几个包资源共享,其他类插件跟systemui都是隔离的
java
private class PluginExceptionHandler implements UncaughtExceptionHandler {
private PluginExceptionHandler() {}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
if (SystemProperties.getBoolean("plugin.debugging", false)) {
return;
}
// Search for and disable plugins that may have been involved in this crash.
boolean disabledAny = checkStack(throwable);
if (!disabledAny) {
// We couldn't find any plugins involved in this crash, just to be safe
// disable all the plugins, so we can be sure that SysUI is running as
// best as possible.
synchronized (this) {
for (PluginActionManager<?> manager : mPluginMap.values()) {
disabledAny |= manager.disableAll();
}
}
}
if (disabledAny) {
throwable = new CrashWhilePluginActiveException(throwable);
}
}
插件崩溃后:
立即禁用: 通过 PluginEnabler.setDisabled() 标记为禁用状态
触发重启: 包装成CrashWhilePluginActiveException异常抛给uncaughtException处理
持久化: 禁用状态会保存,下次启动也不会加载