[关闭]
@guhuizaifeiyang 2017-08-31T08:44:11.000000Z 字数 5850 阅读 1574

设置壁纸流程分析

Android开发


设置静态壁纸

设置静态壁纸有很多途径,但归根结底都是一下三种方法:

  1. 使用WallpaperManagersetResource(int ResourceID)方法
  2. 使用WallpaperManagersetBitmap(Bitmap bitmap)方法
  3. 使用WallpaperManagersetStream(InputStream data)方法

举个栗子,就选第一个:

  1. WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
  2. try {
  3. wallpaperManager.setResource(R.drawable.picture);
  4. } catch (IOException e) {
  5. e.printStackTrace();
  6. }

其他两个和第一个一样,只不过将要设为壁纸的图片参数换成了Bitmap和InputStream。然后就是不要忘了加上以下权限:

  1. <uses-permission android:name = "android.permission.SET_WALLPAPER"/>

时序图镇楼:
此处输入图片的描述

Step 1. setResource()

WallpaperManager.java->setResource()

  1. public void setResource(int resid) throws IOException {
  2. if (sGlobals.mService == null) {
  3. Log.w(TAG, "WallpaperService not running");
  4. return;
  5. }
  6. try {
  7. Resources resources = mContext.getResources();
  8. /* Set the wallpaper to the default values */
  9. // (1)
  10. ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
  11. "res:" + resources.getResourceName(resid));
  12. if (fd != null) {
  13. FileOutputStream fos = null;
  14. try {
  15. fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
  16. setWallpaper(resources.openRawResource(resid), fos);
  17. } finally {
  18. if (fos != null) {
  19. fos.close();
  20. }
  21. }
  22. }
  23. } catch (RemoteException e) {
  24. }
  25. }

(1)
代码里牵涉到sGlobals.mService,这个mService是WallpaperManagerService的实例对象,它是在Globals的构造函数中初始化的。

  1. static class Globals extends IWallpaperManagerCallback.Stub {
  2. private IWallpaperManager mService;
  3. Globals(Looper looper) {
  4. IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
  5. mService = IWallpaperManager.Stub.asInterface(b);
  6. }
  7. }

可以去查看WallpaperManagerService的代码,发现WallpaperManagerService正是实现了IWallpaperManager.Stub,而ServiceManager中正是以关键字Context.WALLPAPER_SERVICE保存的WallpaperManagerService实例,由以上两点可以得知mService正是WallpaperManagerService的实例对象。

Step 2. setWallpaper()

WallpaperManagerService.java->setWallpaper()

  1. public ParcelFileDescriptor setWallpaper(String name, String callingPackage) {
  2. // 检查权限
  3. checkPermission(android.Manifest.permission.SET_WALLPAPER);
  4. if (!isWallpaperSupported(callingPackage)) {
  5. return null;
  6. }
  7. synchronized (mLock) {
  8. if (DEBUG) Slog.v(TAG, "setWallpaper");
  9. // 获取应用的uid
  10. int userId = UserHandle.getCallingUserId();
  11. WallpaperData wallpaper = getWallpaperSafeLocked(userId);
  12. final long ident = Binder.clearCallingIdentity();
  13. try {
  14. ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
  15. if (pfd != null) {
  16. wallpaper.imageWallpaperPending = true;
  17. }
  18. return pfd;
  19. } finally {
  20. Binder.restoreCallingIdentity(ident);
  21. }
  22. }
  23. }

WallpaperManagerService的setWallpaper其实主要作用是取得一个ParcelFileDescriptor对象,这个对象指向了/data/system/user/0/wallpaper这个文件.

这里Binder.getCallingUid()获得应用的uid,我们知道系统的uid是1000,之后应用都是从1000往后的。一些重要的system app,比如com.android.phone电话是1001,com.android.bluetooth蓝牙是1002等等;第三方应用则是从10000开始的,多装一个应用分配的uid就会+1.但是如果应用在清单文件中配置了android:sharedUserId=”android.uid.system”属性,那么这个应用的uid也是1000,不过一般需要打系统签名。
如果要查看应用的uid,可以查看android机器的/data/system/packages.list文件,上面都罗列了每个应用的包名和对应uid。

Step 3. getWallpaperSafeLocked()

WallpaperManagerService.java->getWallpaperSafeLocked()

  1. private WallpaperData getWallpaperSafeLocked(int userId) {
  2. WallpaperData wallpaper = mWallpaperMap.get(userId);
  3. if (wallpaper == null) {
  4. loadSettingsLocked(userId);
  5. wallpaper = mWallpaperMap.get(userId);
  6. }
  7. return wallpaper;
  8. }

首先根据userID从mWallpaperMap对象中获取WallpaperData实例,如果为空则调用loadSettingsLocked方法,再重新获取。那么mWallpaperMap什么时候保存的WallpaperData呢?

从WallpaperManagerService的构造方法中发现:

  1. public WallpaperManagerService(Context context) {
  2. // ......
  3. //生成壁纸相关目录/data/system/users/0
  4. getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
  5. //载入系统保存的配置,UserHandle.USER_OWNER为0
  6. loadSettingsLocked(UserHandle.USER_OWNER);
  7. }
  8. /**
  9. * 返回 /data/system/users/{userId}
  10. * @param userId
  11. * @return /data/system/users/{userId}
  12. */
  13. private static File getWallpaperDir(int userId) {
  14. return Environment.getUserSystemDirectory(userId);
  15. }

Step 4. loadSettingsLocked

  1. private void loadSettingsLocked(int userId) {
  2. JournaledFile journal = makeJournaledFile(userId);
  3. FileInputStream stream = null;
  4. File file = journal.chooseForRead();
  5. if (!file.exists()) {
  6. // This should only happen one time, when upgrading from a legacy system
  7. migrateFromOld();
  8. }
  9. WallpaperData wallpaper = mWallpaperMap.get(userId);
  10. if (wallpaper == null) {
  11. wallpaper = new WallpaperData(userId);
  12. mWallpaperMap.put(userId, wallpaper);
  13. }
  14. boolean success = false;
  15. // 解析/data/system/users/0/wallpaper_info.xml文件
  16. // 省略
  17. }

载入系统保存的配置,这里也是分三步:

  1. 封装一下/data/system/users/0/wallpaper_info.xml文件,用JournaledFile这个工具类,这个工具类会包装两个文件,一个是wallpaper_info.xml正式文件,另一个是wallpaper_info.xml.tmp临时文件。如果正式文件存在就选出正式文件,并删除临时文件;如果正式文件不存在就将临时文件重名为正式文件。
  2. 创建一个WallpaperData并存入mWallpaperMap ,我们可以看看WallpaperData 的构造,这是一个内部类:
  1. static final String WALLPAPER = "wallpaper";
  2. WallpaperData(int userId) {//0
  3. this.userId = userId;
  4. //位于/data/system/users/0/wallpaper,是存放壁纸的文件
  5. wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
  6. }

WallpaperData 的构造方法主要是创建了一个/data/system/users/0/wallpaper的File对象。
3. 最后就是解析/data/system/users/0/wallpaper_info.xml文件

Step 5. updateWallpaperBitmapLocked()

再回到WallpaperManagerService.java->setWallpaper中,查看updateWallpaperBitmapLocked方法。

  1. ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
  2. if (name == null) name = "";
  3. try {
  4. File dir = getWallpaperDir(wallpaper.userId);
  5. if (!dir.exists()) {
  6. dir.mkdir();
  7. FileUtils.setPermissions(
  8. dir.getPath(),
  9. FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
  10. -1, -1);
  11. }
  12. // /data/system/users/0/wallpaper文件
  13. File file = new File(dir, WALLPAPER);
  14. //返回这个文件的fd
  15. ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
  16. MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
  17. if (!SELinux.restorecon(file)) {
  18. return null;
  19. }
  20. wallpaper.name = name;
  21. return fd;
  22. } catch (FileNotFoundException e) {
  23. Slog.w(TAG, "Error setting wallpaper", e);
  24. }
  25. return null;
  26. }

可以看到,这个方法最终返回了/data/system/users/0/wallpaper文件的fd。

Step 6. WallpaperManager->setWallpaper

接着根据ParcelFileDescriptor生成文件输出流fos,再调用resources.openRawResource(resid)获得源壁纸的文件输入流,传入WallpaperManager的下一个方法,setWallpaper(InputStream,FileOutputStream)。
而在这个方法中主要代码为

  1. while ((amt=data.read(buffer)) > 0) {
  2. fos.write(buffer, 0, amt);
  3. if (mSaveBakFlag && null != fos_bak) {
  4. fos_bak.write(buffer, 0, amt);
  5. }
  6. }

WallpaperManager.setResource方法到最后就是进行文件复制而已,把源壁纸图片复制到之前曾经提到过的
/data/system/user/0/wallpaper这个文件中。

设置壁纸的流程走到这里就结束了。。。


那壁纸是如何被加载和绘制的呢?

此过程可以参考:《开机默认壁纸加载流程分析》
https://www.zybuluo.com/guhuizaifeiyang/note/866798

动态壁纸

// TODO

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注