前言
一、设置壁纸
通过系统设置进行锁屏壁纸和桌面壁纸的设置。
Setting 部分的代码:
packages/apps/WallpaperPicker2/src/com/android/wallpaper/module/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 的对象。
packages/apps/WallpaperPicker2/src/com/android/wallpaper/compat/WallpaperManagerCompatV16.java
@Overridepublic 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 类来进行的。
二、锁屏壁纸的显示
锁屏壁纸显示流程图:
上面应用程序设置完成了,下面就该进行壁纸显示了。
WallpaperManager#setStream()
frameworks/base/core/java/android/app/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() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
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() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/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() :
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@Overridepublic 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 itnotifyLockWallpaperChanged();notifyColorsWhich |= FLAG_LOCK;}saveSettingsLocked(wallpaper.userId);// Publish completion *after* we've persisted the changesif (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 itselfif (notifyColorsWhich != 0) {notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);}}
先看锁屏壁纸更新这一部分 notifyLockWallpaperChanged() :
frameworks/base/services/core/java/com/android/server/wallpaper/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}}}@Overridepublic 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 的构造方法里赋值调用:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@Injectpublic 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 Automotivetry {// iWallpaperManager 是 WallpaperManagerService 的 binder对象,// 通过 dagger 在 SystemServicesModule 实例化。iWallpaperManager.setLockWallpaperCallback(this);} catch (RemoteException e) {Log.e(TAG, "System dead?" + e);}}}
所以当锁屏壁纸更新时,就会回调到 LockscreenWallpaper#onWallpaperChanged() :
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@Overridepublic 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() :
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@Overridepublic 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>() {@Overrideprotected LoaderResult doInBackground(Void... params) {return loadBitmap(currentUser, selectedUser);}@Overrideprotected 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()
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/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。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/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()
frameworks/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/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);// 省略部分代码......}