学习笔记:
一、设置壁纸
通过系统设置进行锁屏壁纸和桌面壁纸的设置。
Setting 部分的代码:
// DefaultWallpaperPersister.java
private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup,
int whichWallpaper) {
try {
// whichWallpaper // 壁纸类型
return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup,
whichWallpaper);
} catch (IOException e) {
return 0;
}
}
...
// int whichWallpaper; // 壁纸类型
// if (mDestination == DEST_HOME_SCREEN) { // 桌面壁纸
// whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM;
// } else if (mDestination == DEST_LOCK_SCREEN) { // 锁屏壁纸
// whichWallpaper = WallpaperManagerCompat.FLAG_LOCK;
// } else { // DEST_BOTH // 桌面壁纸 和 锁屏壁纸
// whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM
// | WallpaperManagerCompat.FLAG_LOCK;
// }
...
mWallpaperManagerCompat 其实就是 WallpaperManagerCompatV16 的对象。
// WallpaperManagerCompatV16.java
@Override
public int setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,
int whichWallpaper) throws IOException {
mWallpaperManager.setStream(data);
// Return a value greater than zero to indicate success.
return 1;
}
由此可知,壁纸的设置是通过 WallpaperManager 类来进行的。
二、锁屏壁纸的显示
锁屏壁纸显示流程图: 壁纸显示流程.png上面应用程序设置完成了,下面就该进行壁纸显示了。
WallpaperManager#setStream()
// WallpaperManager.java
public int setStream(InputStream bitmapData, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException {
// 省略部分代码......
try {
//sGlobals.mService即 WallpaperManagerService。
// WallpaperManager 在 SystemServiceRegistry 实例化,
// 过程中传入 WallpaperManagerService 的 binder 对象。
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
mContext.getOpPackageName(), visibleCropHint, allowBackup,
result, which, completion, mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
try {
// 将壁纸copy一份并存储到对应目录,
// 默认是/data/system/users/0/wallpaper(或wallpaper_lock),
// 其中0是主用户的userId,支持多用户
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
copyStreamToWallpaperFile(bitmapData, fos);
fos.close();
completion.waitForCompletion();
} finally {
IoUtils.closeQuietly(fos);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
这里注意两个方法:
sGlobals.mService.setWallpaper()
和fos.close()
。
先看第一个WallpaperManagerService#setWallpaper()
方法:
// WallpaperManagerService.java
@Override
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);
//调用setStream方法的时候参数which必须是正确的
if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {
final String msg = "Must specify a valid wallpaper category to set";
Slog.e(TAG, msg);
throw new IllegalArgumentException(msg);
}
// 省略部分代码......
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
WallpaperData wallpaper;
//如果当前没有锁屏壁纸的话,并且是设置桌面壁纸即which == FLAG_SYSTEM,那么同时设置为锁屏壁纸
if (which == FLAG_SYSTEM && mLockWallpaperMap.get(userId) == null) {
migrateSystemToLockWallpaperLocked(userId);
}
wallpaper = getWallpaperSafeLocked(userId, which);
final long ident = Binder.clearCallingIdentity();
try {
// updateWallpaperBitmapLocked() 将创建一个文件描述符
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()
方法:
// WallpaperManagerService.java
ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
Bundle extras) {
if (name == null) name = "";
try {
// 通过getWallpaperDir() 获取文件路径;这个方法值得注意:后面会讲到。
File dir = getWallpaperDir(wallpaper.userId);
if (!dir.exists()) {
dir.mkdir();
FileUtils.setPermissions(
dir.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
}
// 创建一个文件描述符,并返回。
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;
}
这里再看fos.close()
,这个方法本身没什么可以看的,就是 FileOutputStream 文件字节输出流结束。但是这里涉及到了 WallpaperManagerService 的一个内部类 WallpaperObserver,通过名字我们就能知道它是一个观察者。
WallpaperObserver 初始化:在 WallpaperManagerService 初始化时,会调用 systemReady() 通过getWallpaperSafeLocked()方法初始化 WallpaperData,而这个 WallpaperData 中有个变量 wallpaperObserver ,也在开机时服务初始化, systemReady() 中调用 switchUser() 执行了 wallpaperObserver.startWatching()。
WallpaperObserver 这个内部类的作用:观察壁纸的变化并通知所有 IWallpaperServiceCallbacks 壁纸已经改变。 CREATE 在没有设置壁纸时触发,并且是第一次创建。每次更改壁纸时都会触发 CLOSE_WRITE,这也是关注fos.close()
的原因。
所以文件的变化触发 WallpaperObserver 的 onEvent() :
// WallpaperManagerService.java
@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 事件的文件路径
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 (moved && lockWallpaperChanged) {
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) {
SELinux.restorecon(changedFile);
if (moved) {
loadSettingsLocked(wallpaper.userId, true);
}
generateCrop(wallpaper);
wallpaper.imageWallpaperPending = false;
if (sysWallpaperChanged) {
// 桌面壁纸变化,那么bind ImageWallpaper,ImageWallpaper是负责显示静态桌面壁纸的
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");
}
if (!lockWallpaperChanged) {
//如果参数which是system+lock,也就是同时设置锁屏和桌面壁纸,那么remove锁屏壁纸,因为已经是同一张壁纸了
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()
:
// WallpaperManagerService.java
private void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
try {
cb.onWallpaperChanged();
} catch (RemoteException e) {
// Oh well it went away; no big deal
}
}
}
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
synchronized (mLock) {
mKeyguardListener = cb;
}
return true;
}
notifyLockWallpaperChanged 中执行 cb.onWallpaperChanged();这里的 cb = mKeyguardListener,而 mKeyguardListener 在 setLockWallpaperCallback() 方法中得到。 跟进我们发现 cb 其实就是 LockscreenWallpaper 引用,在 LockscreenWallpaper 的构造方法里赋值调用:
// LockscreenWallpaper.java
@Inject
public LockscreenWallpaper(WallpaperManager wallpaperManager,
@Nullable IWallpaperManager iWallpaperManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
NotificationMediaManager mediaManager,
@Main Handler mainHandler) {
// 省略部分代码......
if (iWallpaperManager != null) {
// Service is disabled on some devices like Automotive
try {
// iWallpaperManager 是 WallpaperManagerService 的 binder对象,
// 通过 dagger 在 SystemServicesModule 实例化。
iWallpaperManager.setLockWallpaperCallback(this);
} catch (RemoteException e) {
Log.e(TAG, "System dead?" + e);
}
}
}
所以当锁屏壁纸更新时,就会回调到 LockscreenWallpaper#onWallpaperChanged() :
// LockscreenWallpaper.java
@Override
public void onWallpaperChanged() {
// Called on Binder thread.
postUpdateWallpaper();
}
private void postUpdateWallpaper() {
if (mH == null) {
Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
return;
}
mH.removeCallbacks(this);
mH.post(this);
}
而 LockscreenWallpaper 类实现了 Runnable 接口的,所以看下它的 run() 方法;LockscreenWallpaper#run():
// LockscreenWallpaper.java
@Override
public void run() {
// Called in response to onWallpaperChanged on the main thread.
if (mLoader != null) {
mLoader.cancel(false /* interrupt */);
}
final int currentUser = mCurrentUserId;
final UserHandle selectedUser = mSelectedUser;
mLoader = new AsyncTask<Void, Void, LoaderResult>() {
@Override
protected LoaderResult doInBackground(Void... params) {
return loadBitmap(currentUser, selectedUser);
}
@Override
protected void onPostExecute(LoaderResult result) {
super.onPostExecute(result);
if (isCancelled()) {
return;
}
if (result.success) {
mCached = true;
mCache = result.bitmap;
mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
// 通知StatusBar更新壁纸
mMediaManager.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
mLoader = null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
异步获取壁纸,并通知StatusBar去更新壁纸。
NotificationMediaManager#updateMediaMetaData()
// NotificationMediaManager.java
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
Trace.beginSection("StatusBar#updateMediaMetaData");
// 省略部分代码......
Bitmap artworkBitmap = null;
if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
if (artworkBitmap == null) {
artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
}
}
//在后台线程上处理图稿并将生成的位图发送到finishUpdateMediaMetaData。
if (metaDataChanged) {
for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
task.cancel(true);
}
mProcessArtworkTasks.clear();
}
if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
allowEnterAnimation).execute(artworkBitmap));
} else {
finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
}
Trace.endSection();
}
对锁屏壁纸所在 view 做 setImageBitmap。
// NotificationMediaManager.java
private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
@Nullable Bitmap bmp) {
Drawable artworkDrawable = null;
if (bmp != null) {
artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
}
// 省略部分代码......
if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
&& (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
&& mBiometricUnlockController != null && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
&& !hideBecauseOccluded) {
// 省略部分代码......
if (metaDataChanged) {
if (mBackdropBack.getDrawable() != null) {
Drawable drawable =
mBackdropBack.getDrawable().getConstantState()
.newDrawable(mBackdropFront.getResources()).mutate();
// 设置壁纸 setImageDrawable()
mBackdropFront.setImageDrawable(drawable);
mBackdropFront.setAlpha(1f);
mBackdropFront.setVisibility(View.VISIBLE);
} else {
mBackdropFront.setVisibility(View.INVISIBLE);
}
if (DEBUG_MEDIA_FAKE_ARTWORK) {
final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
mBackdropBack.setBackgroundColor(0xFFFFFFFF);
mBackdropBack.setImageDrawable(new ColorDrawable(c));
} else {
mBackdropBack.setImageDrawable(artworkDrawable);
}
if (mBackdropFront.getVisibility() == View.VISIBLE) {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
+ mBackdropFront.getDrawable()
+ " to "
+ mBackdropBack.getDrawable());
}
mBackdropFront.animate()
.setDuration(250)
.alpha(0f).withEndAction(mHideBackdropFront);
}
}
} else {
// 省略部分代码......
}
}
通过 mBackdropFront.setImageDrawable(drawable) 方法将图片设置进去,完成锁屏壁纸的更新。
mBackdropFront 在 NotificationMediaManager的setup()
方法被赋值,而 setup() 方法在 StatusBar 的 makeStatusBarView() 中被调用初始化。
StatusBar#makeStatusBarView()
// StatusBar.java
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
// 省略部分代码......
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);
// 省略部分代码......
}
网友评论