SystemUI plugin 开发

SystemUI Plugins

SystemUI插件提供了一种快速替换SystemUI原有组件的方法,可以在运行时更改SystemUI的行为。

现在已有插件:
here

一、使用方法

1. Plugin 接口

在包com.android.systemui.plugins中定义SystemUI 和 Plugin 中共同使用的接口:

@ProvidesInterface(action = MyPlugin.ACTION, version = MyPlugin.VERSION)
@DependsOn(target = OtherInterface.class)
public interface MyPlugin extends Plugin {
    String ACTION = "com.android.systemui.action.PLUGIN_MY_PLUGIN";
    int VERSION = 1;
    ...
}
  • VERSION: 解决兼容性问题,每次增加或者修改接口,VERSION需要增大, 以避免Plugin接口没有默认实现
  • ACTION: 用于关联Plugin apk 中声明的组件

2. Plugin Listener

可以通过 PluginManager注册监听器PluginListener, 可以实现在相应插件安装时调用方法

public interface PluginListener<T extends Plugin> {
    /**
     * Called when the plugin has been loaded and is ready to be used.
     * This may be called multiple times if multiple plugins are allowed.
     * It may also be called in the future if the plugin package changes
     * and needs to be reloaded.
     */
    void onPluginConnected(T plugin);

    /**
     * Called when a plugin has been uninstalled/updated and should be removed
     * from use.
     */
    default void onPluginDisconnected(T plugin) {
        // Optional.
    }
}

3. 使用实例

  1. 在SystemUI编译PluginLib.aar, 让Plugin apk 依赖这个lib

  2. AndroidManifest.xml

       <service android:name=".SampleOverlayPlugin"
            android:label="@string/plugin_label">
            <intent-filter>
                <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
            </intent-filter>
        </service>
  • 可以通过PackageManagerService 去查找组件

  • 可以方便的通过PMS 结果禁用/启用组件

    permission

 <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
  1. 实现接口
@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
public class SampleOverlayPlugin implements OverlayPlugin {
    ...
    public void onCreate(Context sysuiContext, Context pluginContext) {

    }

    public void onDestroy() {

    }
}
plugin.png

二、设计原理

1. crash处理

PluginManagerImpl中定义了一个UncaughtExceptionHandler, 在发生crash 时检查是否为插件引起,如果是,并且插件不在白名单中,则禁用插件

白名单配置:
res/values/config.xml

    <!-- SystemUI Plugins that can be loaded on user builds. -->
    <string-array name="config_pluginWhitelist" translatable="false">
        <item>com.android.systemui</item>
        <item>com.systemui.plugin</item>
    </string-array>

白名单中插件在每次SystemUI 进程启动时加载

2. 插件加载流程

插件加载时机: SystemUI 进程启动 或者 插件apk 安装(监听 Intent.ACTION_PACKAGE_ADDED 广播)

SystemUIService.onCreate

 -> VolumeUI.start()

-> new VolumeDialogComponent() 

-> ExtensionBuilder.withPlugin

-> PluginManagerImpl.addPluginListener

->PluginInstanceManagerFactory.createPluginInstanceManager

一个插件组件对应一个PluginInstanceManager

com/android/systemui/plugins/PluginInstanceManager.java

        protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
            ... ...
            try {
                ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
                // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
                if (mPm.checkPermission(PLUGIN_PERMISSION, pkg)
                        != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Plugin doesn't have permission: " + pkg);
                    return null;
                }
                // Create our own ClassLoader so we can use our own code as the parent.
                //1. 创建plugin classloader
                ClassLoader classLoader = mManager.getClassLoader(info);
                 //2. 创建plugin Context
                Context pluginContext = new PluginContextWrapper(
                        mContext.createApplicationContext(info, 0), classLoader);
                Class<?> pluginClass = Class.forName(cls, true, classLoader);
                // TODO: Only create the plugin before version check if we need it for
                // legacy version check.
                T plugin = (T) pluginClass.newInstance();
                try {
                    //3. 检查版本
                    VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
                    if (DEBUG) Log.d(TAG, "createPlugin");
                    return new PluginInfo(pkg, cls, plugin, pluginContext, version);
                } catch (InvalidVersionException e) {
                    // 处理版本不一致情况 ... 
                  ... ...
            } catch (Throwable e) {
                Log.w(TAG, "Couldn't load plugin: " + pkg, e);
                return null;
            }
        }

这个方法中做了以下工作:

  • 创建plugin classloader

  • 创建plugin Context

  • 检查version :

    版本不一致会弹出通知 :
    "Plugin xxx is too old..." 或者 "Plugin xxx is too new...", 也不会继续加再这个插件了

3. ClassLoader

只有"com.android.systemui.plugin" 包名为这个时,使用SystemUI 类加载器, 即PluginLib 中类

SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java

    private static class ClassLoaderFilter extends ClassLoader {
        private final String mPackage;
        private final ClassLoader mBase;

        public ClassLoaderFilter(ClassLoader base, String pkg) {
            super(ClassLoader.getSystemClassLoader());
            mBase = base;
            mPackage = pkg;
        }

        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
            return mBase.loadClass(name);
        }
    }

4. Plugin Context

Context 创建

                Context pluginContext = new PluginContextWrapper(
                        mContext.createApplicationContext(info, 0), classLoader);

注意事项

1.PluginDependency

SystemUI中 通过 Dependency.get(XXX.class)获取依赖项

Plugin 中可以通过PluginDependency 获取的SystemUI 组件

  • VolumeDialogController - Mostly just API for the volume plugin

  • ActivityStarter - Allows starting of intents while co-operating with keyguard unlocks.

如何添加:

  1. 将接口放到com.android.systemui.plugins包下面
  2. 调用PluginDependencyProvider.allowPluginDependency

src/com/android/systemui/volume/VolumeDialogComponent.java

    @Inject
    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
            VolumeDialogControllerImpl volumeDialogController) {
                ...
                 // Allow plugins to reference the VolumeDialogController.
                Dependency.get(PluginDependencyProvider.class)
                        .allowPluginDependency(VolumeDialogController.class);
            }

2.注解

Requires , DependsOn

@Requires(target = VolumeDialog.class, version = VolumeDialog.VERSION)
@DependsOn(target = Callback.class)
@Requires(target = VolumeDialogController.class, version = VolumeDialogController.VERSION)
@Requires(target = PluginDependency.class, version = PluginDependency.VERSION)
@Dependencies({@DependsOn(
        target = VolumeDialogController.StreamState.class
), @DependsOn(
        target = VolumeDialogController.State.class
), @DependsOn(
        target = VolumeDialogController.Callbacks.class
)})
public class VolumeDialogPlugin implements VolumeDialog {

    private  VolumeDialogController mController;
    @Override
    public void init(int i, Callback callback) {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void onCreate(Context sysuiContext, Context pluginContext) {
    }

    @Override
    public void onDestroy() {
    }

    private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() {
        @Override
        public void onZenSettingsClicked() {
        }

        @Override
        public void onZenPrioritySettingsClicked() {
        }
    };
}

3. ClassNotFoundException

plugin context 中Resources 对象 ClassLoader对象 为null,在加载资源时候,如果没用Context ClassLoader ,如 DrawableInflater ,会抛找不到类异常,用下面方法可以解决


    setResourceClassLoader(context.getResources(), context.getClassLoader());
    

    public void setResourceClassLoader(Resources resources, ClassLoader classLoader) {
        try {
            Field field = Resources.class.getDeclaredField("mClassLoader");
            field.setAccessible(true);
            Object object = field.get(resources);
            field.set(resources, classLoader);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

参考文档

https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/plugins.md

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • Github链接 0 引言 插件化一直以来都被视为Android中一门高深莫测的学问,它需要解决一系列难题: 四大...
    豆沙包67阅读 3,455评论 0 2
  • 什么是组件化和插件化? 组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中...
    Jeeson阅读 3,101评论 1 10
  • 涉及知识点:APM, java Agent, plugin, bytecode, asm, InvocationH...
    zyq_neuq阅读 17,148评论 8 83
  • 0.目录 RePlugin是360公司推出的开源的Android插件化和热更新框架, 广泛运用于360旗下的And...
    骆驼骑士阅读 651评论 0 0
  • 引言 先简单介绍一下Android插件化。很早之前已经有公司在研究这项技术,淘宝做得比较早,但淘宝的这项技术一直是...
    流水潺湲阅读 11,293评论 8 149