学习笔记:
一、设置壁纸
通过系统设置进行锁屏壁纸和桌面壁纸的设置。
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 类来进行的。
二、锁屏壁纸的显示
锁屏壁纸显示流程图:
上面应用程序设置完成了,下面就该进行壁纸显示了。
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); // 省略部分代码...... }