一、设置壁纸流程
系统提供了相应的 API 接口,APP 侧通过 Context.getSystemService(Context.WALLPAPER_SERVICE) 获取 WallpaperManager 对象。WallpaperManager 中可以通过 setBitmap setStream setResource 三种方式进行设置。以 setResource 为例,整个流程大致如下:
不管是 setBitmap setStream setResource 哪种方式,都是调用 WallpaperManagerService 的 setWallpaper 获取 ParcelFileDescriptor 对象
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public int setResource(@RawRes int resid, @SetWallpaperFlags int which)
throws IOException {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
throw new RuntimeException(new DeadSystemException());
}
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
try {
Resources resources = mContext.getResources();
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
"res:" + resources.getResourceName(resid),
mContext.getOpPackageName(), null, false, result, which, completion,
mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
boolean ok = false;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
// The 'close()' is the trigger for any server-side image manipulation,
// so we must do that before waiting for completion.
fos.close();
completion.waitForCompletion();
} finally {
// Might be redundant but completion shouldn't wait unless the write
// succeeded; this is a fallback if it threw past the close+wait.
IoUtils.closeQuietly(fos);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
而 WallpaperMS 的 setWallpaper 中就是获取通过 getWallpaperSafeLocked 获取系统服务 systemReady() 启动初始化的 WallpaperData ,在通过 updateWallpaperBitmapLocked 获取 WallpaperData 的 ParcelFileDescriptor 返回给 WallpaperManager 。ParcelFileDescriptor 继续进行 copyStreamToWallpaperFile 写入覆盖操作,替换壁纸文件。
而这个 WallpaperData 中有个变量 wallpaperObserver ,也在开机时服务初始化 systemReady() 中调用 switchUser() ,已经执行 wallpaperObserver.startWatching() 。所以文件的变化触发 WallpaperObserver 的 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);
final File changedFile = new File(mWallpaperDir, path);
// System and system+lock changes happen on the system wallpaper input file;
// lock-only changes happen on the dedicated lock wallpaper input file
final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
int notifyColorsWhich = 0;
WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
if (DEBUG) {
Slog.v(TAG, "Wallpaper file change: evt=" + event
+ " path=" + path
+ " sys=" + sysWallpaperChanged
+ " lock=" + lockWallpaperChanged
+ " imagePending=" + wallpaper.imageWallpaperPending
+ " whichPending=0x" + Integer.toHexString(wallpaper.whichPending)
+ " written=" + written);
}
if (moved && lockWallpaperChanged) {
// We just migrated sys -> lock to preserve imagery for an impending
// new system-only wallpaper. Tell keyguard about it and make sure it
// has the right SELinux label.
if (DEBUG) {
Slog.i(TAG, "Sys -> lock MOVED_TO");
}
SELinux.restorecon(changedFile);
notifyLockWallpaperChanged();
notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
return;
}
synchronized (mLock) {
if (sysWallpaperChanged || lockWallpaperChanged) {
notifyCallbacksLocked(wallpaper);
if (wallpaper.wallpaperComponent == null
|| event != CLOSE_WRITE // includes the MOVED_TO case
|| wallpaper.imageWallpaperPending) {
if (written) {
// The image source has finished writing the source image,
// so we now produce the crop rect (in the background), and
// only publish the new displayable (sub)image as a result
// of that work.
if (DEBUG) {
Slog.v(TAG, "Wallpaper written; generating crop");
}
SELinux.restorecon(changedFile);
if (moved) {
// This is a restore, so generate the crop using any just-restored new
// crop guidelines, making sure to preserve our local dimension hints.
// We also make sure to reapply the correct SELinux label.
if (DEBUG) {
Slog.v(TAG, "moved-to, therefore restore; reloading metadata");
}
loadSettingsLocked(wallpaper.userId, true);
}
generateCrop(wallpaper);
if (DEBUG) {
Slog.v(TAG, "Crop done; invoking completion callback");
}
wallpaper.imageWallpaperPending = false;
if (sysWallpaperChanged) {
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
false, wallpaper, null);
notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
|| (wallpaper.whichPending & FLAG_LOCK) != 0) {
if (DEBUG) {
Slog.i(TAG, "Lock-relevant wallpaper changed");
}
// either a lock-only wallpaper commit or a system+lock event.
// if it's system-plus-lock we need to wipe the lock bookkeeping;
// we're falling back to displaying the system wallpaper there.
if (!lockWallpaperChanged) {
mLockWallpaperMap.remove(wallpaper.userId);
}
// and in any case, tell keyguard about it
notifyLockWallpaperChanged();
notifyColorsWhich |= FLAG_LOCK;
}
saveSettingsLocked(wallpaper.userId);
// Publish completion *after* we've persisted the changes
if (wallpaper.setComplete != null) {
try {
wallpaper.setComplete.onWallpaperChanged();
} catch (RemoteException e) {
// if this fails we don't really care; the setting app may just
// have crashed and that sort of thing is a fact of life.
}
}
}
}
}
}
// Outside of the lock since it will synchronize itself
if (notifyColorsWhich != 0) {
notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
}
}
}
notifyLockWallpaperChanged 中执行 cb.onWallpaperChanged(); 这里面的 cb 就是 LockscreenWallpaper ,系统开机时在 WallpaperManager setLockWallpaperCallback 中注册的,LockscreenWallpaper 中具体执行在其 run() 函数,完成锁屏壁纸替换操作。
notifyWallpaperColorsChanged 中最终调用到 notifyWallpaperColorsChangedOnDisplay() ,在通知各种注册了监听的模块。具体内容如下问题分析。
二、Android R 版本上设置壁纸时会看到桌面闪烁问题
原因是 Launcher WallpaperColorInfo.java 中添加了 mWallpaperManager.addOnColorsChangedListener 。监听壁纸变化时会根据壁纸颜色的不同,设置不同的 style 然后 recreate() Launcher 。在设置时壁纸,WallpaperManagerService 会先发一次 WallpaperColors 为 null 的,然后才执行 extractColors() 去获取 WallpaperColors 重新发送一次。导致 Launcher 会短时间 recreate() 两次,所以闪烁明显,把系统这里修改掉即可。
private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int which,
int displayId) {
boolean needsExtraction;
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
getWallpaperCallbacks(wallpaper.userId, displayId);
final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
// No-op until someone is listening to it.
if (emptyCallbackList(currentUserColorListeners) &&
emptyCallbackList(userAllColorListeners)) {
return;
}
if (DEBUG) {
Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which);
}
needsExtraction = wallpaper.primaryColors == null;
}
// Let's notify the current values, it's fine if it's null, it just means
// that we don't know yet.
// notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
if (needsExtraction) {
extractColors(wallpaper);
synchronized (mLock) {
// Don't need to notify if nothing changed.
if (wallpaper.primaryColors == null) {
Slog.e(TAG, "extract colors is NULL");
// return;
}
}
}
notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
}
把 notifyColorListeners 移到提取颜色后再调用。这样避免多次调用发送,目前从代码逻辑和实际验证来看,没发现原生设计的意义,发送两次好像没啥用,对于 live wallpaper 来说也是一样的发 null 而已。这块后面又发现再补充。有知道的大佬可以留言下,感谢。