SystemUI深浅色主题随壁纸改变的流程探析
前言
在Android8.1上有一个新增的特性,当你修改了自己的壁纸时(壁纸的主色调必须有差别,比如从白色壁纸更改为黑色壁纸),你的Android设备的下拉QS、音量调节框、关机对话框等的背景色都会随之改变。
深色主题的Quicksettings
并且在9.0系统上,Google还为这个功能添加了一项开关,用户可以强制设置主题背景色:
设置 - 显示 - 设备主题背景
废话不多说,从源码里来看看根据壁纸的这整套流程是怎么实现的。
设置系统壁纸
首先简单介绍下Android如何修改系统壁纸,如果是第三方应用首先要申请系统权限:
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
然后需要通过WallpaperManager相关的接口进行静态壁纸的设置。
WallpaperManager提供了setBitmap(bitmap)、setResource(resid)、setStream(inputstream)这三个方法来设置静态壁纸,下面以setBitmap的源码为例来说明一下。
public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which, int userId)
throws IOException {
...
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
try {
// 通过WallpaperManagerService的setWallpaper方法获得一个文件描述符
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
mContext.getOpPackageName(), visibleCropHint, allowBackup,
result, which, completion, userId);
if (fd != null) {
FileOutputStream fos = null;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
// 将bitmap写入描述符对应的文件里
fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
fos.close();
completion.waitForCompletion();
} finally {
IoUtils.closeQuietly(fos);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
从以上方法来看,设置壁纸的过程有进行写文件操作,我们知道SystemUI的静态壁纸是ImageWallpaper,那么上述过程是如何通知到ImageWallpaper的呢。下面就来介绍WallpaperManagerService里的一个重要组件:WallpaperObserver。
壁纸改变的监听者
WallpaperObserver继承自FileObserver,而FileObserver类是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器,可以监听一个文件或者一个文件夹,并在上述动作发生时回调onEvent()方法。
回到上面讲的setBitmap方法,会通过WallpaperManagerService的setWallpaper()方法获得一个文件描述符,
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
Rect cropHint, boolean allowBackup, Bundle extras, int which,
IWallpaperManagerCallback completion, int userId) {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
checkPermission(android.Manifest.permission.SET_WALLPAPER);
...
synchronized (mLock) {
...
wallpaper = getWallpaperSafeLocked(userId, which);
final long ident = Binder.clearCallingIdentity();
try {
// 创建一个文件描述符
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
if (pfd != null) {
wallpaper.imageWallpaperPending = true;
wallpaper.whichPending = which;
wallpaper.setComplete = completion;
wallpaper.cropHint.set(cropHint);
wallpaper.allowBackup = allowBackup;
}
return pfd;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
再看updateWallpaperBitmapLocked()的实现:
ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
Bundle extras) {
if (name == null) name = "";
try {
File dir = getWallpaperDir(wallpaper.userId);
...
ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
...
return fd;
} catch (FileNotFoundException e) {
Slog.w(TAG, "Error setting wallpaper", e);
}
return null;
}
从以上实现可知,文件路径是用getWallpaperDir()方法获得的,了解了这点,就方便介绍WallpaperObserver了。在它的构造方法里,会将getWallpaperDir()取得的路径传回给父类的构造方法,从而开始监听wallpaper的CLOSE_WRITE事件。
public WallpaperObserver(WallpaperData wallpaper) {
super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
...
}
下面是用于回调的onEvent()方法的实现:
@Override
public void onEvent(int event, String path) {
if (path == null) {
return;
}
final boolean moved = (event == MOVED_TO);
final boolean written = (event == CLOSE_WRITE || moved);
...
// 监听CLOSE_WRITE 通知ImageWallpaper进行壁纸更新
...
// Outside of the lock since it will synchronize itself
if (notifyColorsWhich != 0) {
// Android 8.1才新增的通知壁纸颜色变化的方法
notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
}
}
notifyColorsWhich 是一个int值,初始时为0,当通过Wallpaper path取到的新壁纸File与之前保存的全局变量不一致时,即有壁纸更新,notifyColorsWhich 会与FLAG_SYSTEM进行按位或运算;
notifyWallpaperColorsChanged()方法又会将参数传给notifyColorListeners()方法去处理,这里就直接跳到notifyColorListeners()的实现:
private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
int userId) {
final IWallpaperManagerCallback keyguardListener;
final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
mColorsChangedListeners.get(userId);
...
keyguardListener = mKeyguardListener;
if (currentUserColorListeners != null) {
final int count = currentUserColorListeners.beginBroadcast();
for (int i = 0; i < count; i++) {
colorListeners.add(currentUserColorListeners.getBroadcastItem(i));
}
currentUserColorListeners.finishBroadcast();
}
...
}
final int count = colorListeners.size();
for (int i = 0; i < count; i++) {
try {
// 通知系统壁纸颜色改变
colorListeners.get(i).onWallpaperColorsChanged(wallpaperColors, which, userId);
} catch (RemoteException e) {
...
}
}
...
}
notifyWallpaperColorsChanged()方法会分别通知系统壁纸和锁屏壁纸颜色改变的监听者,我们暂时只先分析系统壁纸相关的流程。在这个方法里,首先会通过mColorsChangedListeners取得当前userId的一个RemoteCallbackList,这个类是一个系统层的容器类,容纳的对象是一些接口mCallbacks ,用于执行列表中对象的回调函数,里面有几个比较重要的方法:
- register():向 mCallbacks中添加接口对象,mCallbacks是一个HashMap,以binder和callback为键值对。
- unregister():从mCallbacks中通过remove删除接口。
- beginBroadcast():创建一个回调列表mCallbacks的副本active[],并返回数组的大小,之后可以使用getBroadcastItem检索条目。
注意,一次只能激活一个广播,因此必须确保始终从相同的线程调用(通常通过Handler进行调度)或者自己做同步。完成后必须调用finishBroadcast()。 - finishBroadcast():清理我们之前建立的active[]副本。
- getBroadcastItem():从副本中获取之前存入的接口对象。
看的出WallPaperObserver的currentUserColorListeners里存放的是一些IWallpaperManagerCallback对象,之后通过getBroadcastItem()遍历出所有的callbacks,并分别调用每个callback的onWallpaperColorsChanged()。
SystemUI的注册与响应过程
StatusBar的onStart()方法中有这么一步:
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
SysuiColorExtractor继承于ColorExtractor,后者又实现了WallpaperManager.OnColorsChangedListener
public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) {
super(context, type);
...
WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
if (wallpaperManager != null) {
// Listen to all users instead of only the current one.
wallpaperManager.removeOnColorsChangedListener(this);
wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
UserHandle.USER_ALL);
}
}
在SysuiColorExtractor的构造函数里,通过WallpaperManager的addOnColorsChangedListener()方法,把自身这个OnColorsChangedListener给注册过去,最终会调用到WallpaperManager.sGlobals的addOnColorsChangedListener():
private static class Globals extends IWallpaperManagerCallback.Stub {
private final IWallpaperManager mService;
...
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
@Nullable Handler handler, int userId) {
synchronized (this) {
if (!mColorCallbackRegistered) {
try {
mService.registerWallpaperColorsCallback(this, userId);
...
} catch (RemoteException e) {
...
}
}
// 把callback保存下来,这个callback是ColorExtractor
mColorListeners.add(new Pair<>(callback, handler));
}
}
}
然后会把自己这个IWallpaperManagerCallback以及userId,通过WallpaperManagerService的registerWallpaperColorsCallback()方法传递过去:
public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, true, "registerWallpaperColorsCallback", null);
synchronized (mLock) {
RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
mColorsChangedListeners.get(userId);
if (userColorsChangedListeners == null) {
userColorsChangedListeners = new RemoteCallbackList<>();
mColorsChangedListeners.put(userId, userColorsChangedListeners);
}
userColorsChangedListeners.register(cb);
}
}
到这一步就和上一节的通知过程对应上了,给mColorsChangedListeners添加新的RemoteCallbackList对象,并注册相应的callback进去(这里的callback是一个Globals对象),方便后续通知。
好了,注册过程讲完了,最后再来看看SystemUI是怎么响应壁纸颜色改变的。回到上一节的最末尾,我们说到 “通过getBroadcastItem()遍历出所有的callbacks,并分别调用每个callback的onWallpaperColorsChanged()”,已经知道这一处的callback是WallpaperManager里的Globals类的对象了,找到它的实现:
@Override
public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
synchronized (this) {
for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) {
Handler handler = listener.second;
if (listener.second == null) {
handler = mMainLooperHandler;
}
handler.post(() -> {
// Dealing with race conditions between posting a callback and
// removeOnColorsChangedListener being called.
boolean stillExists;
synchronized (sGlobals) {
stillExists = mColorListeners.contains(listener);
}
if (stillExists) {
listener.first.onColorsChanged(colors, which, userId);
}
});
}
}
}
在前面的注释中已经介绍过,存在listener这个Pair里的first是一个ColorExtractor,回到SystemUI,
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
...
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = colors;
GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
extractInto(colors, systemColors[TYPE_NORMAL], systemColors[TYPE_DARK],
systemColors[TYPE_EXTRA_DARK]);
changed = true;
}
if (changed) {
triggerColorsChanged(which);
}
}
protected void triggerColorsChanged(int which) {
ArrayList<WeakReference<OnColorsChangedListener>> references =
new ArrayList<>(mOnColorsChangedListeners);
final int size = references.size();
for (int i = 0; i < size; i++) {
final WeakReference<OnColorsChangedListener> weakReference = references.get(i);
final OnColorsChangedListener listener = weakReference.get();
if (listener == null) {
mOnColorsChangedListeners.remove(weakReference);
} else {
listener.onColorsChanged(this, which);
}
}
}
public interface OnColorsChangedListener {
void onColorsChanged(ColorExtractor colorExtractor, int which);
}
遍历mOnColorsChangedListeners里的所有listener并调用其onColorsChanged()方法,在这一节的最开始我们有提到StatusBar把自己作为参数通过addOnColorsChangedListener()传给了ColorExtractor,之后便被加入到mOnColorsChangedListeners里面,过程很简单,这里就不再赘述了。之后自然是调用StatusBar的onColorsChanged()了:
@Override
public void onColorsChanged(ColorExtractor extractor, int which) {
updateTheme();
}
/**
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
final boolean inflated = mStackScroller != null;
// The system wallpaper defines if QS should be light or dark.
WallpaperColors systemColors = mColorExtractor
.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
final boolean useDarkTheme = systemColors != null
&& (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
if (isUsingDarkTheme() != useDarkTheme) {
mUiOffloadThread.submit(() -> {
try {
mOverlayManager.setEnabled("com.android.systemui.theme.dark",
useDarkTheme, mLockscreenUserManager.getCurrentUserId());
} catch (RemoteException e) {
Log.w(TAG, "Can't change theme", e);
}
});
}
...
if (inflated) {
int which;
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
which = WallpaperManager.FLAG_LOCK;
} else {
which = WallpaperManager.FLAG_SYSTEM;
}
final boolean useDarkText = mColorExtractor.getColors(which,
true /* ignoreVisibility */).supportsDarkText();
mStackScroller.updateDecorViews(useDarkText);
// Make sure we have the correct navbar/statusbar colors.
mStatusBarWindowManager.setKeyguardDark(useDarkText);
}
}
最后在updateTheme()这个方法中修改SystemUI相关的深色和浅色主题。
以上就是整个注册以及响应的流程了,至于到了updateTheme()之后SystemUI是以怎样的方式去具体修改那些控件的主题的,后续有时间会继续再研究一下。