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. 使用實例
在SystemUI編譯PluginLib.aar, 讓Plugin apk 依賴這個lib
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" />
- 實現(xiàn)接口
@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
public class SampleOverlayPlugin implements OverlayPlugin {
...
public void onCreate(Context sysuiContext, Context pluginContext) {
}
public void onDestroy() {
}
}

二、設(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.
如何添加:
- 將接口放到com.android.systemui.plugins包下面
- 調(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