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

标签(空格分隔): Android开发


[参考资料]
Android壁纸开发流程分析 android壁纸服务流程浅析 深入理解Android卷III 第八章深入理解Android壁纸



  • 开机默认壁纸加载流程分析
  • 开机加载壁纸
  • Step 1. new WallpaperManagerService()
  • Step 2. getWallpaperDir()
  • Step 3. loadSettingsLocked()
  • Step 4. wallpaperF.systemRunning()
  • Step 5. switchWallpaper()
  • Step 6. WallpaperManagerService&WallpaperObserver
  • Step 7. bindWallpaperComponentLocked()
  • Step 8. onServiceConnected()
  • Step 9. attachServiceLocked()
  • Step 10. attach()
  • Step 11. executeMessage()
  • Step 12. onCreateEngine()
  • Step 13. WallpaperService$Engine->attach()
  • Step 14. updateSurfaceSize()
  • Step 15. getCurrentWallpaperLocked
  • Step 16. getWallpaper()
  • Step 17. getDefaultWallpaperLocked()
  • Step 18. updateSurface()
  • 案例分析


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

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

  • frameworks/base/core/java/android/app/WallpaperManager.java
    API类,提供了各种函数接口为应用开发者所使用。
  • frameworks/base/services/java/com/android/server/WallpaperManagerService.java
    系统服务,用于管理壁纸的运行与切换。
  • frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
    同SystemUI一样,壁纸运行在一个Android服务之中,这个服务的名字叫做WallpaperService。当用户选择了一个壁纸之后,此壁纸所对应的WallpaperService便会启动并开始进行壁纸的绘制工作
  • frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
    类ImageWallpaper实现了上面的WallpaperService。

下面是WallpaperManagerService的启动时序图:

如何开机启动android service 如何开机启动wallpaperengine_android

如何开机启动android service 如何开机启动wallpaperengine_java_02

开机加载壁纸

Step 1. new WallpaperManagerService()

SystemServer.java->startOtherServices()

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

WallpaperManagerService.java->WallpaperManagerService()

public WallpaperManagerService(Context context) {
    mImageWallpaper = ComponentName.unflattenFromString(
            context.getResources().getString(R.string.image_wallpaper_component));
    // 获取WindowManagerService的实例
    mIWindowManager = IWindowManager.Stub.asInterface(
            ServiceManager.getService(Context.WINDOW_SERVICE));
    // ...
    getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
    loadSettingsLocked(UserHandle.USER_OWNER);
}

public static final @UserIdInt int USER_OWNER = 0;

Step 2. getWallpaperDir()

WallpaperManagerService.java->getWallpaperDir()

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

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

Step 3. loadSettingsLocked()

WallpaperManagerService.java->loadSettingsLocked()

private void loadSettingsLocked(int userId) {
    // (1)
    JournaledFile journal = makeJournaledFile(userId);
    FileInputStream stream = null;
    File file = journal.chooseForRead();
    if (!file.exists()) {
        // This should only happen one time, when upgrading from a legacy system
        migrateFromOld();
    }

    // (2)
    WallpaperData wallpaper = mWallpaperMap.get(userId);
    if (wallpaper == null) {
        wallpaper = new WallpaperData(userId);
        mWallpaperMap.put(userId, wallpaper);
    }
    boolean success = false;

    // (3) 解析/data/system/users/0/wallpaper_info.xml文件
    // 省略

    // 第一次启动时,wallpaer_info.xml不存在,也就不采用其里面的值。这时调用getMaximumSizeDimension来赋值。
    int baseSize = getMaximumSizeDimension();
    if (wallpaper.width < baseSize) {
        wallpaper.width = baseSize;
    }
    if (wallpaper.height < baseSize) {
        wallpaper.height = baseSize;
    }
}

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

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

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

2.创建一个WallpaperData并存入mWallpaperMap ,我们可以看看WallpaperData 的构造函数,这是一个内部类:

static final String WALLPAPER = "wallpaper";
WallpaperData(int userId) {//0
  this.userId = userId;
  //位于/data/system/users/0/wallpaper,是存放壁纸的文件
  wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
}

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/目录下:

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

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

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<wp id="1" width="720" height="1280" cropLeft="0" cropTop="0" cropRight="1280" cropBottom="1280" name="" backup="true" />
<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()

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

WallpaperManagerService.java->systemRunning()

public void systemRunning() {
    if (DEBUG) Slog.v(TAG, "systemReady");
    WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
    switchWallpaper(wallpaper, null);
    wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
    wallpaper.wallpaperObserver.startWatching();
    // ...
}

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

Step 5. switchWallpaper()

WallpaperManagerService.java->switchWallpaper()

void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
    synchronized (mLock) {
        RuntimeException e = null;
        try {
            ComponentName cname = wallpaper.wallpaperComponent != null ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
            if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
                return;
            }
        } catch (RuntimeException e1) {
            e = e1;
        }
        Slog.w(TAG, "Failure starting previous wallpaper", e);
        clearWallpaperLocked(false, wallpaper.userId, reply);
    }
}

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

Step 6. WallpaperManagerService&WallpaperObserver

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

private class WallpaperObserver extends FileObserver {
    final WallpaperData mWallpaper;
    final File mWallpaperDir;
    final File mWallpaperFile;
    final File mWallpaperInfoFile;

    public WallpaperObserver(WallpaperData wallpaper) {
        // 监听/data/system/users/0/下CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF几个事件
        super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
                CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
        mWallpaperDir = getWallpaperDir(wallpaper.userId);
        mWallpaper = wallpaper;
        mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
        mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
    }

    @Override
    public void onEvent(int event, String path) {
        if (path == null) {
            return;
        }
        synchronized (mLock) {
            // ...
            // 绑定壁纸组件
            File changedFile = new File(mWallpaperDir, path);
            if (mWallpaperFile.equals(changedFile)) {
                bindWallpaperComponentLocked(mImageWallpaper, true,
                                false, mWallpaper, null);
                // 保存壁纸信息,和loadSettingsLocked相反。
                saveSettingsLocked(mWallpaper);
            }
        }
    }
}

当/data/system/users/0/wallpaper发生改动时,则需要绑定壁纸组件,调用bindWallpaperComponentLocked方法。这里mImageWallpaper是个ComponentName,指向com.android.systemui/com.android.systemui.ImageWallpaper。

Step 7. bindWallpaperComponentLocked()

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

boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
        boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
    // ...
    try {
         /* 当componentName为null时表示使用默认壁纸。
          这里会将componentName參数改为默认壁纸的componentName */
        if (componentName == null) {
             /* 首先会尝试从com.android.internal.R.string.default_wallpaper_component
              中获取默认壁纸的componentName,这个值的设置位于res/values/config.xml中。*/
            // 这里获取的componentName=null
            componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
            if (componentName == null) {
                // 使用静态壁纸
                // Fall back to static image wallpaper
                componentName = mImageWallpaper;
                if (DEBUG) Slog.v(TAG, "Using image wallpaper");
            }
        }

        // ...
        // 对ComponentName所描写叙述的Service进行一系列的验证,以确保它是一个壁纸服务。

        // WallpaperConnection实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态。
        WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
        intent.setComponent(componentName);
        // ...

        /* 启动指定的壁纸服务。当服务启动完毕后,剩下的启动流程会在WallpaperConnection.onServiceConnected()中继续 */
        if (!mContext.bindServiceAsUser(intent, newConn,
                Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                        | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                new UserHandle(serviceUserId))) {
            // ...
        }
         // 新的的壁纸服务启动成功后。便通过detachWallpaperLocked()销毁旧有的壁纸服务
        if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
                detachWallpaperLocked(mLastWallpaper);
        }

         // 将新的壁纸服务的执行信息保存到WallpaperData中
        wallpaper.wallpaperComponent = componentName;
        wallpaper.connection = newConn;       

        // 最后向WMS申请注冊一个WALLPAPER类型的窗体令牌。
        mIWindowManager.addWindowToken(newConn.mToken,
                WindowManager.LayoutParams.TYPE_WALLPAPER);
}

Step 8. onServiceConnected()

WallpaperManagerService.java$WallpaperConnection->onServiceConnected()

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    synchronized (mLock) {
        if (mWallpaper.connection == this) {
            // ImageWallpaper的onBind方法返回值就是这个mService
            mService = IWallpaperService.Stub.asInterface(service);
            attachServiceLocked(this, mWallpaper);
            saveSettingsLocked(mWallpaper);
        }
    }
}

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

Step 9. attachServiceLocked()

WallpaperManagerService.java->attachServiceLocked()

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

Step 10. attach()

WallpaperService$IWallpaperServiceWrapper->attach

public void attach(IWallpaperConnection conn, IBinder windowToken,
        int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
    // 使用WallpaperManagerService提供的參数构造一个IWallpaperEngineWarapper实例
    new IWallpaperEngineWrapper(mTarget, conn, windowToken,
            windowType, isPreview, reqWidth, reqHeight, padding);
}

IWallpaperEngineWrapper(WallpaperService context,
        IWallpaperConnection conn, IBinder windowToken,
        int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
    // 类成员变量初始化
    // ...

    Message msg = mCaller.obtainMessage(DO_ATTACH);
    mCaller.sendMessage(msg);
}

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

Step 11. executeMessage()

WallpaperService$IWallpaperEngineWrapper->executeMessage()

public void executeMessage(Message message) {
    switch (message.what) {
        case DO_ATTACH: {
            try {
                // 把IWallpaperEngineWrapper实例传递给WallpaperConnection进行保存。
                mConnection.attachEngine(this);
            } catch (RemoteException e) {
                Log.w(TAG, "Wallpaper host disappeared", e);
                return;
            }
            // 通过onCreateEngine()方法创建一个Engine。WallpaperService仅仅是提供壁纸执行的场所,而Engine才是真正的壁纸的实现
            Engine engine = onCreateEngine();
            mEngine = engine;
            mActiveEngines.add(engine);
            engine.attach(this);
            return;
        }
        // ...
    }
}

Step 12. onCreateEngine()

ImageWallpaper.java->onCreateEngine():

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

@Override
public Engine onCreateEngine() {
    // 创建了一个DrawableEngine实例
    mEngine = new DrawableEngine();
    return mEngine;
}

Step 13. WallpaperService$Engine->attach()

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

void attach(IWallpaperEngineWrapper wrapper) {
    // ...
    // ...
    // Engine的初始化工作
    onCreate(mSurfaceHolder);

    mInitializing = false;
    mReportedVisible = false;
    // 绘制壁纸
    updateSurface(false, false, false);
}

Step 14. updateSurfaceSize()

ImageWallpaper$DrawableEngine->updateSurfaceSize():

@Override
public void onCreate(SurfaceHolder surfaceHolder) {
    // ...
    updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo());
}


void updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo) {
    // ...
    // 将mWallpaper和mDefaultWallpaper重置为null
    WallpaperManager.forgetLoadedWallpaper();
    updateWallpaperLocked();
    ...
}

private void updateWallpaperLocked() {
    // ...

    mBackground = mWallpaperManager.getBitmap();
    // ...
}

public Bitmap getBitmap() {
    return sGlobals.peekWallpaperBitmap(mContext, true);
}

public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
    synchronized (this) {
        // 如果已经加载了壁纸,则直接返回。
        if (mWallpaper != null) {
            return mWallpaper;
        }
        // 如果没有加载壁纸,则返回默认壁纸。
        if (mDefaultWallpaper != null) {
            return mDefaultWallpaper;
        }
        mWallpaper = null;
        try {
            // 首次开机,这里获取的wallpaper=null
            mWallpaper = getCurrentWallpaperLocked(context);
        } catch (OutOfMemoryError e) {
            Log.w(TAG, "No memory load current wallpaper", e);
        }
        if (returnDefault) {
            if (mWallpaper == null) {
                mDefaultWallpaper = getDefaultWallpaperLocked(context);
                return mDefaultWallpaper;
            } else {
                mDefaultWallpaper = null;
            }
        }
        return mWallpaper;
    }
}

Step 15. getCurrentWallpaperLocked

private Bitmap getCurrentWallpaperLocked(Context context) {
    if (mService == null) {
        Log.w(TAG, "WallpaperService not running");
        return null;
    }

    try {
        Bundle params = new Bundle();
        // 获取/data/system/users/0/wallpaper的文件描述符
        ParcelFileDescriptor fd = mService.getWallpaper(this, params);
        if (fd != null) {
            try {
                BitmapFactory.Options options = new BitmapFactory.Options();
                return BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, options);
            } catch (OutOfMemoryError e) {
                Log.w(TAG, "Can't decode file", e);
            } finally {
                try {
                    fd.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    } catch (RemoteException e) {
        // Ignore
    }
    return null;
}

Step 16. getWallpaper()

WallpaperManagerService.java->getWallpaper()

public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
        Bundle outParams) {
    synchronized (mLock) {
        // ...
        // 从mWallpaperMap中获取对应用户的WallpapaerData
        WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
        if (wallpaper == null) {
            return null;
        }

        // 从WallpaperData中获取壁纸的尺寸信息并保存在outParms中
        try {
            if (outParams != null) {
                outParams.putInt("width", wallpaper.width);
                outParams.putInt("height", wallpaper.height);
            }
            wallpaper.callbacks.register(cb);
            File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER);
            if (!f.exists()) {
                return null;
            }
            // 将文件包装为一个文件描述符并返回给调用者
            return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
        } catch (FileNotFoundException e) {
            /* Shouldn't happen as we check to see if the file exists */
            Slog.w(TAG, "Error getting wallpaper", e);
        }
        return null;
    }
}

Step 17. getDefaultWallpaperLocked()

private Bitmap getDefaultWallpaperLocked(Context context) {
    InputStream is = openDefaultWallpaper(context);
    // ...
}

public static InputStream openDefaultWallpaper(Context context) {
    // 配置wallpaper路径
    // ... 

    return context.getResources().openRawResource(
            com.android.internal.R.drawable.default_wallpaper);
}

Step 18. updateSurface()

WallpaperService$Engine->updateSurface():

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


案例分析

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

// Solution
WallpaperManager.java->getDefaultWallpaperLocked:

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

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