[关闭]
@guhuizaifeiyang 2017-08-31T08:36:27.000000Z 字数 14065 阅读 3660

开机默认壁纸加载流程分析

Android开发


Android壁纸开发流程分析
android壁纸服务流程浅析
深入理解Android卷III 第八章深入理解Android壁纸


本文讨论的是开机默认壁纸的加载流程,这里只分析静态壁纸。静态壁纸是运行于SystemUI进程中的一个名为ImageWallpaper的特殊WallpaperService。

Android的壁纸功能的实现主要由散布在下面几个文件中的类来完成:
// TODO 类图

下面是WallpaperManagerService的启动时序图:
此处输入图片的描述

此处输入图片的描述

开机加载壁纸

Step 1. new WallpaperManagerService()

SystemServer.java->startOtherServices()

  1. try {
  2. Slog.i(TAG, "Wallpaper Service");
  3. wallpaper = new WallpaperManagerService(context);
  4. ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
  5. } catch (Throwable e) {
  6. reportWtf("starting Wallpaper Service", e);
  7. }

WallpaperManagerService.java->WallpaperManagerService()

  1. public WallpaperManagerService(Context context) {
  2. mImageWallpaper = ComponentName.unflattenFromString(
  3. context.getResources().getString(R.string.image_wallpaper_component));
  4. // 获取WindowManagerService的实例
  5. mIWindowManager = IWindowManager.Stub.asInterface(
  6. ServiceManager.getService(Context.WINDOW_SERVICE));
  7. // ...
  8. getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
  9. loadSettingsLocked(UserHandle.USER_OWNER);
  10. }
  11. public static final @UserIdInt int USER_OWNER = 0;

Step 2. getWallpaperDir()

WallpaperManagerService.java->getWallpaperDir()

  1. private static File getWallpaperDir(int userId) {
  2. // 返回系统的用户目录
  3. return Environment.getUserSystemDirectory(userId);
  4. }

这里返回的结果是:/data/system/users/0。所以Step1中getWallpaperDir(UserHandle.USER_OWNER).mkdirs();就是创建了:/data/system/users/0/这个目录。

Step 3. loadSettingsLocked()

WallpaperManagerService.java->loadSettingsLocked()

  1. private void loadSettingsLocked(int userId) {
  2. // (1)
  3. JournaledFile journal = makeJournaledFile(userId);
  4. FileInputStream stream = null;
  5. File file = journal.chooseForRead();
  6. if (!file.exists()) {
  7. // This should only happen one time, when upgrading from a legacy system
  8. migrateFromOld();
  9. }
  10. // (2)
  11. WallpaperData wallpaper = mWallpaperMap.get(userId);
  12. if (wallpaper == null) {
  13. wallpaper = new WallpaperData(userId);
  14. mWallpaperMap.put(userId, wallpaper);
  15. }
  16. boolean success = false;
  17. // (3) 解析/data/system/users/0/wallpaper_info.xml文件
  18. // 省略
  19. // 第一次启动时,wallpaer_info.xml不存在,也就不采用其里面的值。这时调用getMaximumSizeDimension来赋值。
  20. int baseSize = getMaximumSizeDimension();
  21. if (wallpaper.width < baseSize) {
  22. wallpaper.width = baseSize;
  23. }
  24. if (wallpaper.height < baseSize) {
  25. wallpaper.height = baseSize;
  26. }
  27. }

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

1.用JournaledFile这个工具类封装/data/system/users/0/wallpaper_info.xml文件。这个工具类会包装两个文件,一个是wallpaper_info.xml正式文件,另一个是wallpaper_info.xml.tmp临时文件。如果正式文件存在就选出正式文件,并删除临时文件;如果正式文件不存在就将临时文件重名为正式文件。

  1. private static JournaledFile makeJournaledFile(int userId) {
  2. final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
  3. return new JournaledFile(new File(base), new File(base + ".tmp"));
  4. }
  5. static final String WALLPAPER_INFO = "wallpaper_info.xml";

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文件

Android系统的壁纸信息存放在/data/system/users/0/目录下,WallpaperManagerService启动后,会生成如下两个文件在/data/system/users/0/目录下:

  1. static final String WALLPAPER = wallpaper; //设置的壁纸图片,一般为jpeg格式
  2. static final String WALLPAPER_INFO = wallpaper_info.xml; //包含墙纸的规格信息:高、宽

Wallpaper_info.xml的解析可以查看WallpaperManagerService的loadSettingsLocked()方法。

  1. <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
  2. <wp id="1" width="720" height="1280" cropLeft="0" cropTop="0" cropRight="1280" cropBottom="1280" name="" backup="true" />
  3. <kwp id="2" width="1280" height="1280" cropLeft="0" cropTop="0" cropRight="0" cropBottom="0" name="" />

第一次启动时,wallpaer_info.xml不存在,也就不采用其里面的值。这时,则采用的是getMaximumSizeDimension,也就是背景图的宽和高由getMaximumSizeDimension决定。

分析到这里,我们看到,除了创建了WallpaperData,并没有其它和加载壁纸相关的操作。并且,这里创建的WallpaperData还未填充壁纸的有效信息。
所以,接下来我们从另外一条线索开始分析。壁纸经常伴随着创建、移动、切换、删除等操作,那么肯定会有一个监听机制来监听壁纸的这一系列动作。于是,我们从WallpaperManagerService类的systemRunning方法中找到了一个WallpaperObserver类。

Step 4. wallpaperF.systemRunning()

SystemServer.java->startOtherServices()

  1. try {
  2. if (wallpaperF != null) wallpaperF.systemRunning();
  3. } catch (Throwable e) {
  4. reportWtf("Notifying WallpaperService running", e);
  5. }

WallpaperManagerService.java->systemRunning()

  1. public void systemRunning() {
  2. if (DEBUG) Slog.v(TAG, "systemReady");
  3. WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
  4. switchWallpaper(wallpaper, null);
  5. wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
  6. wallpaper.wallpaperObserver.startWatching();
  7. // ...
  8. }

wallpaperObserver是监听壁纸变化的重要组件,后面会详细介绍。

Step 5. switchWallpaper()

WallpaperManagerService.java->switchWallpaper()

  1. void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
  2. synchronized (mLock) {
  3. RuntimeException e = null;
  4. try {
  5. ComponentName cname = wallpaper.wallpaperComponent != null ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
  6. if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
  7. return;
  8. }
  9. } catch (RuntimeException e1) {
  10. e = e1;
  11. }
  12. Slog.w(TAG, "Failure starting previous wallpaper", e);
  13. clearWallpaperLocked(false, wallpaper.userId, reply);
  14. }
  15. }

switchWallpaper中正常执行到bindWallpaperComponentLocked就会返回了,bindWallpaperComponentLocked的工作是启动一个新的壁纸服务并终止旧的壁纸服务,只有启动壁纸异常的情况下才会执行下面的clearWallpaperLocked清理操作。

Step 6. WallpaperManagerService&WallpaperObserver

WallpaperObserver是一个内部类,用来监听壁纸的变化。首次设置壁纸时会调用CREATE事件,CLOSE_WRITE事件在壁纸每次变化时都会被调用。

  1. private class WallpaperObserver extends FileObserver {
  2. final WallpaperData mWallpaper;
  3. final File mWallpaperDir;
  4. final File mWallpaperFile;
  5. final File mWallpaperInfoFile;
  6. public WallpaperObserver(WallpaperData wallpaper) {
  7. // 监听/data/system/users/0/下CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF几个事件
  8. super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
  9. CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
  10. mWallpaperDir = getWallpaperDir(wallpaper.userId);
  11. mWallpaper = wallpaper;
  12. mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
  13. mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
  14. }
  15. @Override
  16. public void onEvent(int event, String path) {
  17. if (path == null) {
  18. return;
  19. }
  20. synchronized (mLock) {
  21. // ...
  22. // 绑定壁纸组件
  23. File changedFile = new File(mWallpaperDir, path);
  24. if (mWallpaperFile.equals(changedFile)) {
  25. bindWallpaperComponentLocked(mImageWallpaper, true,
  26. false, mWallpaper, null);
  27. // 保存壁纸信息,和loadSettingsLocked相反。
  28. saveSettingsLocked(mWallpaper);
  29. }
  30. }
  31. }
  32. }
  33. 当/data/system/users/0/wallpaper发生改动时,则需要绑定壁纸组件,调用bindWallpaperComponentLocked方法。这里mImageWallpaper是个ComponentName,指向com.android.systemui/com.android.systemui.ImageWallpaper

Step 7. bindWallpaperComponentLocked()

bindWallpaperComponentLocked()方法将会启动由ComponentName所指定的WallpaperService,并向WMS申请用于加入壁纸窗体的窗体令牌。

  1. boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
  2. boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
  3. // ...
  4. try {
  5. /* 当componentName为null时表示使用默认壁纸。
  6. 这里会将componentName參数改为默认壁纸的componentName */
  7. if (componentName == null) {
  8. /* 首先会尝试从com.android.internal.R.string.default_wallpaper_component
  9. 中获取默认壁纸的componentName,这个值的设置位于res/values/config.xml中。*/
  10. // 这里获取的componentName=null
  11. componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
  12. if (componentName == null) {
  13. // 使用静态壁纸
  14. // Fall back to static image wallpaper
  15. componentName = mImageWallpaper;
  16. if (DEBUG) Slog.v(TAG, "Using image wallpaper");
  17. }
  18. }
  19. // ...
  20. // 对ComponentName所描写叙述的Service进行一系列的验证,以确保它是一个壁纸服务。
  21. // WallpaperConnection实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态。
  22. WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
  23. intent.setComponent(componentName);
  24. // ...
  25. /* 启动指定的壁纸服务。当服务启动完毕后,剩下的启动流程会在WallpaperConnection.onServiceConnected()中继续 */
  26. if (!mContext.bindServiceAsUser(intent, newConn,
  27. Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
  28. | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
  29. new UserHandle(serviceUserId))) {
  30. // ...
  31. }
  32. // 新的的壁纸服务启动成功后。便通过detachWallpaperLocked()销毁旧有的壁纸服务
  33. if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
  34. detachWallpaperLocked(mLastWallpaper);
  35. }
  36. // 将新的壁纸服务的执行信息保存到WallpaperData中
  37. wallpaper.wallpaperComponent = componentName;
  38. wallpaper.connection = newConn;
  39. // 最后向WMS申请注冊一个WALLPAPER类型的窗体令牌。
  40. mIWindowManager.addWindowToken(newConn.mToken,
  41. WindowManager.LayoutParams.TYPE_WALLPAPER);
  42. }

Step 8. onServiceConnected()

WallpaperManagerService.java$WallpaperConnection->onServiceConnected()

  1. @Override
  2. public void onServiceConnected(ComponentName name, IBinder service) {
  3. synchronized (mLock) {
  4. if (mWallpaper.connection == this) {
  5. // ImageWallpaper的onBind方法返回值就是这个mService
  6. mService = IWallpaperService.Stub.asInterface(service);
  7. attachServiceLocked(this, mWallpaper);
  8. saveSettingsLocked(mWallpaper);
  9. }
  10. }
  11. }

WallpaperConnection它不仅实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态。在服务绑定成功后的WallpaperConnection.onServiceConnected()方法调用中,WallpaperConnection的实例会被发送给WallpaperService,使其作为WallpaperService向WallpaperManagerService进行通信的桥梁。

Step 9. attachServiceLocked()

WallpaperManagerService.java->attachServiceLocked()

  1. void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
  2. try {
  3. // 这里会调用IWallpaperServiceWrapper的attach方法,将创建壁纸窗体所需的信息发送给壁纸服务。
  4. conn.mService.attach(conn, conn.mToken,
  5. WindowManager.LayoutParams.TYPE_WALLPAPER, false,
  6. wallpaper.width, wallpaper.height, wallpaper.padding);
  7. } catch (RemoteException e) {
  8. // ...
  9. }
  10. }
  11. }

Step 10. attach()

WallpaperService$IWallpaperServiceWrapper->attach

  1. public void attach(IWallpaperConnection conn, IBinder windowToken,
  2. int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
  3. // 使用WallpaperManagerService提供的參数构造一个IWallpaperEngineWarapper实例
  4. new IWallpaperEngineWrapper(mTarget, conn, windowToken,
  5. windowType, isPreview, reqWidth, reqHeight, padding);
  6. }
  7. IWallpaperEngineWrapper(WallpaperService context,
  8. IWallpaperConnection conn, IBinder windowToken,
  9. int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
  10. // 类成员变量初始化
  11. // ...
  12. Message msg = mCaller.obtainMessage(DO_ATTACH);
  13. mCaller.sendMessage(msg);
  14. }

接下来分析DO_ATTACH消息的处理:

Step 11. executeMessage()

WallpaperService$IWallpaperEngineWrapper->executeMessage()

  1. public void executeMessage(Message message) {
  2. switch (message.what) {
  3. case DO_ATTACH: {
  4. try {
  5. // 把IWallpaperEngineWrapper实例传递给WallpaperConnection进行保存。
  6. mConnection.attachEngine(this);
  7. } catch (RemoteException e) {
  8. Log.w(TAG, "Wallpaper host disappeared", e);
  9. return;
  10. }
  11. // 通过onCreateEngine()方法创建一个Engine。WallpaperService仅仅是提供壁纸执行的场所,而Engine才是真正的壁纸的实现
  12. Engine engine = onCreateEngine();
  13. mEngine = engine;
  14. mActiveEngines.add(engine);
  15. engine.attach(this);
  16. return;
  17. }
  18. // ...
  19. }
  20. }

Step 12. onCreateEngine()

ImageWallpaper.java->onCreateEngine():

WallpaperService类中的onCreateEngine方法是一个抽象方法,ImageWallpaper类中实现了此方法。回想一下,在bindWallpaperComponentLocked方法中我们绑定的就是ImageWallpaper服务。

  1. @Override
  2. public Engine onCreateEngine() {
  3. // 创建了一个DrawableEngine实例
  4. mEngine = new DrawableEngine();
  5. return mEngine;
  6. }

Step 13. WallpaperService$Engine->attach()

Engine创建完毕之后会通过Engine.attach()方法完毕Engine的初始化工作。

  1. void attach(IWallpaperEngineWrapper wrapper) {
  2. // ...
  3. // ...
  4. // Engine的初始化工作
  5. onCreate(mSurfaceHolder);
  6. mInitializing = false;
  7. mReportedVisible = false;
  8. // 绘制壁纸
  9. updateSurface(false, false, false);
  10. }

Step 14. updateSurfaceSize()

ImageWallpaper$DrawableEngine->updateSurfaceSize():

  1. @Override
  2. public void onCreate(SurfaceHolder surfaceHolder) {
  3. // ...
  4. updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo());
  5. }
  6. void updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo) {
  7. // ...
  8. // 将mWallpaper和mDefaultWallpaper重置为null
  9. WallpaperManager.forgetLoadedWallpaper();
  10. updateWallpaperLocked();
  11. ...
  12. }
  13. private void updateWallpaperLocked() {
  14. // ...
  15. mBackground = mWallpaperManager.getBitmap();
  16. // ...
  17. }
  18. public Bitmap getBitmap() {
  19. return sGlobals.peekWallpaperBitmap(mContext, true);
  20. }
  21. public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
  22. synchronized (this) {
  23. // 如果已经加载了壁纸,则直接返回。
  24. if (mWallpaper != null) {
  25. return mWallpaper;
  26. }
  27. // 如果没有加载壁纸,则返回默认壁纸。
  28. if (mDefaultWallpaper != null) {
  29. return mDefaultWallpaper;
  30. }
  31. mWallpaper = null;
  32. try {
  33. // 首次开机,这里获取的wallpaper=null
  34. mWallpaper = getCurrentWallpaperLocked(context);
  35. } catch (OutOfMemoryError e) {
  36. Log.w(TAG, "No memory load current wallpaper", e);
  37. }
  38. if (returnDefault) {
  39. if (mWallpaper == null) {
  40. mDefaultWallpaper = getDefaultWallpaperLocked(context);
  41. return mDefaultWallpaper;
  42. } else {
  43. mDefaultWallpaper = null;
  44. }
  45. }
  46. return mWallpaper;
  47. }
  48. }

Step 15. getCurrentWallpaperLocked

  1. private Bitmap getCurrentWallpaperLocked(Context context) {
  2. if (mService == null) {
  3. Log.w(TAG, "WallpaperService not running");
  4. return null;
  5. }
  6. try {
  7. Bundle params = new Bundle();
  8. // 获取/data/system/users/0/wallpaper的文件描述符
  9. ParcelFileDescriptor fd = mService.getWallpaper(this, params);
  10. if (fd != null) {
  11. try {
  12. BitmapFactory.Options options = new BitmapFactory.Options();
  13. return BitmapFactory.decodeFileDescriptor(
  14. fd.getFileDescriptor(), null, options);
  15. } catch (OutOfMemoryError e) {
  16. Log.w(TAG, "Can't decode file", e);
  17. } finally {
  18. try {
  19. fd.close();
  20. } catch (IOException e) {
  21. // Ignore
  22. }
  23. }
  24. }
  25. } catch (RemoteException e) {
  26. // Ignore
  27. }
  28. return null;
  29. }

Step 16. getWallpaper()

WallpaperManagerService.java->getWallpaper()

  1. public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
  2. Bundle outParams) {
  3. synchronized (mLock) {
  4. // ...
  5. // 从mWallpaperMap中获取对应用户的WallpapaerData
  6. WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
  7. if (wallpaper == null) {
  8. return null;
  9. }
  10. // 从WallpaperData中获取壁纸的尺寸信息并保存在outParms中
  11. try {
  12. if (outParams != null) {
  13. outParams.putInt("width", wallpaper.width);
  14. outParams.putInt("height", wallpaper.height);
  15. }
  16. wallpaper.callbacks.register(cb);
  17. File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER);
  18. if (!f.exists()) {
  19. return null;
  20. }
  21. // 将文件包装为一个文件描述符并返回给调用者
  22. return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
  23. } catch (FileNotFoundException e) {
  24. /* Shouldn't happen as we check to see if the file exists */
  25. Slog.w(TAG, "Error getting wallpaper", e);
  26. }
  27. return null;
  28. }
  29. }

Step 17. getDefaultWallpaperLocked()

  1. private Bitmap getDefaultWallpaperLocked(Context context) {
  2. InputStream is = openDefaultWallpaper(context);
  3. // ...
  4. }
  5. public static InputStream openDefaultWallpaper(Context context) {
  6. // 配置wallpaper路径
  7. // ...
  8. return context.getResources().openRawResource(
  9. com.android.internal.R.drawable.default_wallpaper);
  10. }

Step 18. updateSurface()

WallpaperService$Engine->updateSurface():

Engine的updateSurface()方法将会创建壁纸窗体及Surface,并进行壁纸的绘制。


案例分析

// Question
根据机器颜色加载相应的壁纸

// Solution
WallpaperManager.java->getDefaultWallpaperLocked:

  1. private Bitmap getDefaultWallpaperLocked(Context context) {
  2. try {
  3. InputStream is = context.getResources().openRawResource( com.android.internal.R.drawable.default_wallpaper);
  4. }

直接修改com.android.internal.R.drawable.default_wallpaper。

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