SystemUI plugin 開發(fā)

SystemUI Plugins

SystemUI插件提供了一種快速替換SystemUI原有組件的方法,可以在運行時更改SystemUI的行為。

現(xiàn)在已有插件:
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接口沒有默認(rèn)實現(xiàn)
  • ACTION: 用于關(guān)聯(lián)Plugin apk 中聲明的組件

2. Plugin Listener

可以通過 PluginManager注冊監(jiān)聽器PluginListener, 可以實現(xiàn)在相應(yīng)插件安裝時調(diào)用方法

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 結(jié)果禁用/啟用組件

    permission

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

    }

    public void onDestroy() {

    }
}
plugin.png

二、設(shè)計原理

1. crash處理

PluginManagerImpl中定義了一個UncaughtExceptionHandler, 在發(fā)生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 安裝(監(jiān)聽 Intent.ACTION_PACKAGE_ADDED 廣播)

SystemUIService.onCreate

 -> VolumeUI.start()

-> new VolumeDialogComponent() 

-> ExtensionBuilder.withPlugin

-> PluginManagerImpl.addPluginListener

->PluginInstanceManagerFactory.createPluginInstanceManager

一個插件組件對應(yīng)一個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. 創(chuàng)建plugin classloader
                ClassLoader classLoader = mManager.getClassLoader(info);
                 //2. 創(chuàng)建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;
            }
        }

這個方法中做了以下工作:

  • 創(chuàng)建plugin classloader

  • 創(chuàng)建plugin Context

  • 檢查version :

    版本不一致會彈出通知 :
    "Plugin xxx is too old..." 或者 "Plugin xxx is too new...", 也不會繼續(xù)加再這個插件了

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 創(chuàng)建

                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. 調(diào)用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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容