动态壁纸的设置

Posted by DoubleWay on May 22, 2020

静态壁纸和动态壁纸看起来差别很大,但其实两者的性质是差不多的,都是以service的形式运行在后台,在类型为TYPE_WALLPAPER窗口上绘制内容,静态壁纸就是一种特殊的动态壁纸

壁纸的绘制由WallpaperService来控制,继承和实现WallpaperEngine是壁纸相关开发的第一步,Engine是WallpaperService里面的一个内部类,还提供了一系列可以让子类重写的回调。

WallpaperManagerService用来管理壁纸的运行和切换,通过WallpaperManager向外提供用户操作壁纸的接口,切换壁纸的时候会取消当前壁纸的WallpaperService绑定,重新启动新的WallpaperService

动态壁纸服务的启动流程

启动动态壁纸可以通过调用WallpaperManagerService.setWallpaperComponent()方法来完成,这里是首先在WallpaperManager里面定义的这个方法

/frameworks/base/core/java/android/app/WallpaperManager.java

/**
 * Set the live wallpaper.
 *
 * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
 * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
 * another user's wallpaper.
 *
 * @hide
 */
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
@UnsupportedAppUsage
public boolean setWallpaperComponent(ComponentName name, int userId) {
    if (sGlobals.mService == null) {
        Log.w(TAG, "WallpaperService not running");
        throw new RuntimeException(new DeadSystemException());
    }
    try {
        sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
                userId);
        return true;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

在方法中调用了sGlobals.mService.setWallpaperComponentChecked,这里的是调用了

private static class Globals extends IWallpaperManagerCallback.Stub {
    private final IWallpaperManager mService;

mService(IWallpaperManager)

@Override public void setWallpaperComponentChecked(android.content.ComponentName name, java.lang.String callingPackage, int userId) throws android.os.RemoteException
{}
/**
     * Set the live wallpaper. This only affects the system wallpaper.
     */
@Override public void setWallpaperComponent(android.content.ComponentName name) throws android.os.RemoteException
{

这里运用了aidl的知识。 WallpaperManagerService 继承了 IWallpaperManager.Stub

frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

private void setWallpaperComponent(ComponentName name, int userId) {
    
    //...省略代码
    
    synchronized (mLock) {
        if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
        wallpaper = mWallpaperMap.get(userId);
        
     //...省略代码

        try {
            wallpaper.imageWallpaperPending = false;
            boolean same = changingToSame(name, wallpaper);
            if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
                if (!same) {
                    wallpaper.primaryColors = null;
                    
    //...省略代码
}

在里面有几处关键地方:

  1. mWallpaperMap.get(userId)通过userId来获取壁纸的信息

    因为WallpaperManagerService支持多用户机制,每个用户都可以设置自己的壁纸,mWallpaperMap为每个用户保存了一个WallpaperData实例,里面包含壁纸的状态和相关运行信息

  2. bindWallpaperComponentLocked来绑定服务,启动新壁纸的WallpaperService

    具体的过程包括:

    • 认证服务,会进行一系列的认证检查,确认是壁纸服务后,才会启动WallpaperService
      1. 服务必须是以android.permission.BIND_WALLPAPER作为访问权限,该权限必须通过WallpaperService服务来请求,只有系统才能用,防止被其他应用意外启动

        if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission))
        
      2. 服务必须声明可以处理android.service.wallpaper.WallpaperService这个action,因为WallpaperManagerService会使用这个action进行绑定

          public static final String SERVICE_INTERFACE =   "android.service.wallpaper.WallpaperService";
         Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
      
      1. 服务必须在AndroidManifest.xml中提供android.service.wallpaper的meta-data
    • 创建WallpaperConnection,其实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态,同时还实现了IWallpaperConnection.stub

          WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper, componentUid);
      
       class WallpaperConnection extends IWallpaperConnection.Stub
               implements ServiceConnection {          
      
    • 调用mContext.bindServiceAsUser启动指定的服务。之后的流程会在WallpaperConnection.onServiceConnected回调中完成。

    • 新的壁纸服务启动之后,销毁旧的壁纸服务

    • 将新的壁纸服务的信息保存到WallpaperData中

      wallpaper.wallpaperComponent = componentName;
      wallpaper.connection = newConn; //wallpaper就是WallpaperData
      
  3. 传递创建窗口所需信息

    仅仅将指定的壁纸服务启动起来还不能让壁纸显示出来,因为还没有窗口令牌而无法添加窗口。所以这后半部流程会在WallpaperConnection的onServiceConnected方法回调中进行。

    在WallpaperService的onBind方法中会返回一个IWallpaperServiceWrapper实例。这个类继承了IWallpaperService.stub。保存了Wallpaper的实例,同时也实现了唯一的一个接口attach()。

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.  Subclasses should not override.
     */
    @Override
    public final IBinder onBind(Intent intent) {
        return new IWallpaperServiceWrapper(this);
    }
    

    WallpaperService.attach方法中的各参数意义是:

    @Override
    public void attach(IWallpaperConnection conn, IBinder windowToken,
            int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
            int displayId) {
        mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
                windowType, isPreview, reqWidth, reqHeight, padding, displayId);
    }
       
    //conn:WallpaperConnection。继承自IWallpaperConnection,只提供了两个接口定义:attachEngine和engineShown。
    //conn.Token:向WindowManagerService申请的令牌
    //WindowManager.LayoutParams.TYPE_WALLPAPER:指明需要添加TYPE_WALLPAPER类型的窗口。另一种情况是壁纸预览的时候,会使用TYPE_APPLICATION_MEDIA_OVERLAY类型创建窗口,此时壁纸服务创建的窗口将会以子窗口的形式衬在LivePicker的窗口之下。
    //isPreview:实际作为壁纸的时候是false,壁纸预览的时候是true
    

    WallpaperManagerService会在WallpaperConnection.onServiceConnected方法中收到回调,然后会进行以下三步:

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mLock) {
            if (mWallpaper.connection == this) {
                mService = IWallpaperService.Stub.asInterface(service);
                attachServiceLocked(this, mWallpaper);
                // XXX should probably do saveSettingsLocked() later
                // when we have an engine, but I'm not sure about
                // locking there and anyway we always need to be able to
                // recover if there is something wrong.
                if (!mWallpaper.equals(mFallbackWallpaper)) {
                    saveSettingsLocked(mWallpaper.userId);
                }
                FgThread.getHandler().removeCallbacks(mResetRunnable);
            }
        }
    }
    
    1. 将WallpaperService传回的IWallpaperService接口保存为mService

    2. 绑定壁纸服务,调用attachServiceLocked方法,这个方法会调用IWallpaperService.attach方法来传递壁纸服务创建窗口的信息

    3. saveSettingLocked,保存壁纸运行状态到文件系统中

  4. 创建Engine

    调用IWallpaperService.attach是WallpaperManagerService与WallpaperService的第一次接触。该方法会创建IWallpaperEngineWrapper,其继承自IWallpaperEngine.stub,支持跨进程调用。在其中会创建并封装Engine的实例。Engine创建完成之后会通过Engine.attach来初始化Engine,步骤如下:

    1. 设置必要的信息,包括:
      • mSurfaceHolder:BaseSurfaceHolder类型的内部类实例,可以通过它来定制需要的Surface类型
      • 获取WindowSession,用于与WMS通信
      • mWindow.setSession(mSession):用于接受WMS的回调
      • 设置监听屏幕状态,保证屏幕关闭之后停止动画渲染节省电量
    2. 调用Engine.onCreate,重写的子类一般需要重写该方法来修改mSurfaceHolder的属性。此时还没有创建窗口,修改的属性会在窗口创建时生效。
    3. updateSurface:根据SurfaceHolder的属性创建窗口以及Surface,并进行壁纸的第一次绘制