SystemUI插件开发指南

在 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() 注册包变更广播接收器

资源及异常隔离

  1. 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处理

持久化: 禁用状态会保存,下次启动也不会加载

相关推荐
Dovis(誓平步青云)1 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码3 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓8 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1239 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium