Android中,壁纸分为动态壁纸和静态壁纸两种。静态壁纸是一张图片,动态壁纸是以动画为表现形式,有的可以对用户的操作作出反应。二者表现形式看似差异很大,但是二者的本质是统一的: 它们都以一个Service的形式运行在系统后台,并在一个类型为TYPE_WALLPAPER的窗口上绘制内容。 实质上,静态壁纸是一种特殊的动态壁纸。
Android壁纸管理的三个层次:
启动动态壁纸可以通过调用WallpaperManagerService.setWallpaperComponent()方法来完成,这个方法的步骤如下:
1)首先调用mWallpaperMap.get(UserId)来获取壁纸的运行信息。
Q:为什么需要根据UserID来划分壁纸的运行信息?
A:WallpaperManagerService支持多用户机制,设备上的每个用户都可以设置自己的壁纸。mWallpaperMap为每个用户保存了一个WallpaperData实例,其中保存了和壁纸状态相关的运行信息:WallpaperService的ComponentName、ServiceConnection等。当发生用户切换的时候,获取到相应用户的WallpaperData,然后获取ComponentName,这样就可以重新启动用户的壁纸。
2)调用bindWallpaperComponentLocked方法,启动新壁纸的WallpaperService。
这个过程首先会对服务进行一系列认证,确认是一个壁纸服务之后才会启动WallpaperService,检查的内容如下:
服务满足条件之后,就会着手启动目标服务并绑定,步骤如下:
创建WallpaperConnection,其实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态,同时还实现了IWallpaperConnection.stub,支持跨进程通信。服务绑定成功之后,会在onServiceConnected调用中被发送给WallpaperService,成为WallpaperService和WallpaperManagerService之间通信的桥梁。
调用mContext.bindServiceAsUser启动指定的服务。之后的流程会在WallpaperConnection.onServiceConnected回调中完成。
新的壁纸服务启动之后,销毁旧的壁纸服务
将新的壁纸服务的信息保存到WallpaperData中
在WallpaperData中会有一个lastDiedTime属性,描述壁纸服务的存活时间,如果小于一定的数值就会认为这个壁纸服务不可靠从而选择默认壁纸。
向WindowManagerService申请注册一个WALLPAPER类型的窗口令牌,其会再onServiceConnected之后被传递给WallpaperService作为添加窗口的令牌
仅仅将指定的壁纸服务启动起来还不能让壁纸显示出来,因为还没有窗口令牌而无法添加窗口。所以这后半部流程会在WallpaperConnection的onServiceConnected方法回调中进行。
在WallpaperService的onBind方法中会返回一个IWallpaperServiceWrapper实例。这个类继承了IWallpaperService.stub。保存了Wallpaper的实例,同时也实现了唯一的一个接口attach()。
WallpaperManagerService会在WallpaperConnection.onServiceConnected方法中收到回调,然后会进行以下三步:
其中IWallpaperService.attach方法中的参数意义如下:
conn:WallpaperConnection。继承自IWallpaperConnection,只提供了两个接口定义:attachEngine和engineShown。
Q:为什么有了WallpaperManager这个对外界的标准接口还需要WallpaperConnection?
A:attachEngine和engineShown只有WallpaperService才用得到,并且是与WallpaperManagerService之间底层且私密的交流,不适合放在通用接口之中。WallpaperService只是一个承载壁纸运行的容器,真正实现壁纸的核心为Engine类,当WallpaperService创建完Engine之后,就会通过attachEngine方法将Engine对象引用交给WallpaperManagerService。
conn.Token:向WindowManagerService申请的令牌
WindowManager.LayoutParams.TYPE_WALLPAPER:指明需要添加TYPE_WALLPAPER类型的窗口。另一种情况是壁纸预览的时候,会使用TYPE_APPLICATION_MEDIA_OVERLAY类型创建窗口,此时壁纸服务创建的窗口将会以子窗口的形式衬在LivePicker的窗口之下。
isPreview:实际作为壁纸的时候是false,壁纸预览的时候是true
调用IWallpaperService.attach是WallpaperManagerService与WallpaperService的第一次接触。该方法会创建IWallpaperEngineWrapper,其继承自IWallpaperEngine.stub,支持跨进程调用。在其中会创建并封装Engine的实例。
IWallpaperEngineWrapper在attach方法中只创建了对象,但是没有将其赋给任何变量。这个实例对象的保持依靠的是其内部的HandlerCaller以及HandlerCaller中的Handler的外部引用持有来实现的。Handler是HandlerCaller的内部类,其中包含了HandlerCaller的隐式引用,而HandlerCaller又持有IWallpaperEngineWrapper的引用,所以在内部Handler处理DO_ATTACH消息之前,IWallpaperEngineWrapper不会被垃圾回收器回收。
在IWallpaperEngineWrapper中创建的HandlerCaller是Handler的一个封装,比Handler额外提供一个executeOrSendMessage方法,在HandlerCaller所在线程执行该方法的时候会使处理函数马上执行,在其他线程中与Handler.sendMessage一样。
这个HandlerCaller是一个重要的线程调度器,所有与壁纸相关的操作都会以消息的形式发送给mCaller,然后在IWallpaperEngineWrapper的executeMessage方法中处理,这些操作也就转移到了mCaller所在线程。默认情况下mCaller运行在主线程中。
然后就是处理DO_ATTACH消息,会进行如下步骤:
mConnection.attachEngine:把IWallpaperEngineWrapper实例传递给WallpaperConnnection,这之后就不用担心实例被回收了。
通过onCreateEngine创建一个Engine。这个方法由开发者自己实现
将新建的Engine添加到WallpaperService.mActiveEngine中。
当使用壁纸预览的时候,WallpaperService仍然只有一个,但是其内部会变成两个Engine。这也说明了WallpaperService仅仅是提供壁纸运行的场所,真正壁纸的实现是Engine。
Engine创建完成之后会通过Engine.attach来初始化Engine,步骤如下:
使用到的Binder对象:
Q:WallpaperService和WallpaperManagerService之间除了IWallpaperService之外为什么要多加一个IWallpaperEngineWrapper?
A:首先,从WallpaperManagerService的角度来看,IWallpaperService代表的是WallpaperService,是壁纸实现的容器。而IWallpaperEngineWrapper代表是Engine,是壁纸的真正实现。
其次,WallpaperService中可以运行多个Engine实例,但是WallpaperManagerService或者LivePicker关注的只是一个确定的Engine实例,而不是WallpaperService中的所有Engine,从这个角度来说,也是简化实现的一种方式。
使用到的数据结构:
该方法中更新surface的条件有:
这些条件只要有一个发生了变化就要更新Surface,SurfaceHolder允许修改的属性有:
完成Surface更新的原理:
完成Surface更新以后,updateSurface会触发SurfaceHolder的回调通知所有的SurfaceHolder的使用者(开发者自定义的Engine)。这些回调包括:
另外还有一个在壁纸销毁时触发的回调:onSurfaceDestroyed。这些回调给开发者提供了Surface的生命周期。
对Engine的回调做个总结:
- onCreate:发生在Engine.attach方法中,表示Engine生命周期的开始
- onDestroy:发生在Engine.detach方法中,表示Engine生命周期的结束
- onSurfaceCreated / onSurfaceChanged / onSurfaceDestroyed / onSurfaceRedraw-Needed:发生在updateSurface和Engine.detach中,向开发者描述了Surface的生命周期
- onVisibilityChanged:通知当前壁纸的可见性发生了变化。这里的可见性指的是是否以壁纸窗口作为当前窗口的背景。因为使窗口从不可见到可见的花销巨大,会经历窗口的重新布局、Surface的创建、等待窗口完成第一次绘制等等,所以Android索性使窗口始终可见,只是在不需要显示壁纸的时候将它放在所有窗口的底部,使用户看不到(调用Surface.hide将壁纸的Surface隐藏,需要的时候调用Surface.show,这样Surface的内容并没有被清除,不需要重绘Engine对象),但是如果壁纸动画还在运行的话,仍然会消耗资源,所以当onVisibilityChanged报告壁纸可见性为false的时候,Engine必须停止一切消耗资源的操作。
- onTouchEvent:用于处理用户的触摸事件。开发者可以通过Engine.setTouchEventEnabled方法在壁纸窗口的flags中增加或者删除FLAG_NOT_TOUCHABLE标记,从而设置壁纸窗口是否可以接受用户的输入事件。InputDispatcher查找触摸事件的派发目标是根据窗口的Z序从上向下遍历查找触摸事件落在其上的第一个窗口。但是InputDispatcher为壁纸窗口做了特殊化处理,InputDispatcher::findTouchedWindowTargetLocked找到触摸目标之后会判断该窗口是否要显示壁纸,如果要的话就把触摸事件同时发送给两个窗口。
- onOffsetChanged:用于通知Engine对象需要对壁纸所绘制的内容进行偏移,发生于WallpaperManager.setWallpaperOffsets()。当要求显示壁纸的窗口内容发生滚动的时候,可能希望壁纸的内容也随之偏移。这个偏移量的表现形式没有限定,可以自由发挥。
- onCommand:用于处理WallpaperManager.sendWallpaperCommand发送的命令,同样可以自定义命令,提供了一种通信方式。
- onDesiredSizeChanged:显示壁纸的窗口调用WallpaperManager.suggestDediredDimensions指明它所期望壁纸的尺寸大小,可以配合setWallpaperOffsets来实现壁纸与内容的同步滚动。
壁纸销毁的工作就是把壁纸创建的过程反过来进行一遍:
在WallpaperService中则是:
所谓静态壁纸,就是SystemUI中的一个名为ImageWallpaper的特殊动态壁纸而已,实现的架构就是动态壁纸的架构,只不过其内容是一张静态的图片而已。之所以与普通的动态壁纸区分,是因为Android为ImageWallpaper提供了很多方便的API,使得可以方便地将静态图片设置为壁纸。
旧版本API中(29以前)):
静态壁纸的服务位于SystemUI中,ImageWallpaper继承自WallpaperService,在其onCreateEngine方法中创建了一个继承自Engine的WallpaperEngine。
WallpaperEngine中有一个Bitmap类型的成员变量mBackground,这就是作为壁纸的位图。对静态壁纸进行重绘的时候,就会调用drawFrameLocked方法将mBackground绘制到壁纸窗口的Surface上。
静态壁纸的设置不像动态壁纸那样需要签名级系统权限,只需要有android.permission.SET_WALLPAPER权限就可以通过WallpaperManager的相关接口设置静态壁纸。WallpaperManager提供了setBitmap、setResource以及setStream三个方法进行静态壁纸的设置。以setBitmap为例,会进行如下步骤:
- 通过WallpaperManagerService.setWallpaper方法获取一个文件描述符,这个文件的位置为/data/(secure)/system//wallpaper文件夹。
- 将Bitmap写入这个文件描述符所指向的文件中。
通过WallpaperManager设置完壁纸的文件位置之后,ImageWallpaper怎么知道壁纸文件路径发生了变化呢?Android提供了一个WallpaperObserver类(WallpaperManagerService.WallpaperObserver),该类继承自FileObserver,这个类是Android提供的一个工具类,用于监听文件系统中发生的文件创建、删除与修改事件。FileObserver可以监听一个文件夹或者是一个文件,当它发生变化的时候,onEvent回调就会根据参数获取到发生这些动作的文件路径。在壁纸监听中主要是CLOSE_WRITE事件,除此之外还有DELETE、DELETE_SELF事件,这两个事件的原因是因为位图被保存在文件系统中,保护非常弱,需要处理其被删除的情况。
在WallpaperObserver的onEvent方法中最终调用的是bindWallpaperComponentLocked方法,这也就意味着在壁纸文件位置重新设置之后,不会通知原有的DrawableEngine进行更新和重绘,而是销毁原有的DrawableEngine,创建一个新的进行壁纸绘制。
DrawableEngine设置壁纸的步骤如下:
- 调用forgetLoadedWallpaper,将mDefaultWallpaper和mWallpaper设置成null。这两个变量一个存储系统默认的壁纸,另一个存储现有的设置壁纸。
- 调用WallpaperManager.getBitmap获取作为壁纸的位图,在这个方法里又会调用Globals.peekWallpaperBitmap方法。在其中有两套保证静态壁纸稳定运行的机制:缓存以及备用方案。它会尽量返回已经加载的位图,如果失败就会返回默认的位图。
- 获取被设置为壁纸的位图使用的是Globals.getCurrentWallpaperLocked方法。这个方法会去前面WallpaperManager设置的文件位置加载位图并设置为壁纸。
API29中:
静态壁纸的服务仍然位于SytemUI中,ImageWallpaper继承自WallpaperService,在其onCreateEngine方法中创建的Engine换成了GLEngine:
class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener
这个类继承自Engine类,同时扩展了GLWallpaperRenderer类的SurfaceProxy接口以及StateListner接口。
GLWallpaperRenderer类是一个渲染器,负责发送OpenGl调用来渲染一个帧。而它的SurfaceProxy接口则是拥有SurfaceHolder的代理。通过这个接口能够将OpenGl的渲染帧发送到Surface上。
StateListener接口位于StatusBarStateController中,用于接受状态栏状态更新以及Dozing 的状态改变。
因为这个类的改变,现在静态壁纸的创建流程变成了:
ImageWallpaperRenderer类继承了GLWallpaperRenderer类,在它的构造方法中调用loadBitmap方法进行了Bitmap的加载。
在这个方法中,首先就是调用了WallpaperManager的getBitmap方法去获取Bitmap。getBitmap中又调用了Wallpaper的静态内部类Globals的peekWallpaperBitmap方法,其同旧版本一样提供了两套保证尽态比值稳定运行的机制:缓存和备用方案。如果缓存为空的话就调用getCurrentWallpaperLocked方法来从文件中获取Bitmap。
从文件中获取Bitmap的流程相对于老版本没有改变太多,只是不再直接在WallpaperManagerService.getWallpaper方法中调用getWallpaperDir来获取文件路径以及句柄了,而是将这个获取过程移到了WallpaperData的初始化中。而Wallpaper对象又会在loadSetttingLocked方法中被创建。在这个方法中会做这么几件事:
loadingSettingLocked方法会在很多地方被调用,但是这里只关注三个地方(我认为。。):
接收到返回的文件句柄之后,就会在Globals类中使用BitmapFactory.decodeFileDescriptor进行解码生成Bitmap文件然后一路返回到ImageWallpaperRender中。然后就会调用forgetLoadedWallpaper方法删除掉所有上一张加载的壁纸的内部引用。调用这个方法对于那些只想短暂拥有壁纸,想要减少内存消耗的应用是有用的。但是在调用这个方法之后想要调用这张壁纸又要从硬盘中重新读取。
都结束之后就将Surface的大小调整为Bitmap的宽高。至此Bitmap的获取就结束了。
获取完Bitmap,之后的壁纸渲染工作GLEngine都会交给GLWallpaperRenderer的各种方法,由其发送OpenGL调用来渲染,并通过SurfaceProxy来对SurfaceHolder进行处理,来将壁纸渲染到Surface上。
设置壁纸的过程和老版本基本一致。
WMS对于壁纸窗口的特殊处理体现在三个方面:
壁纸的存在意义就是给其他窗口提供背景,一个窗口希望壁纸作为其壁纸的时候,就可以将FLAG_SHOW_WALLPAPER加入到它的flag中。当WMS检测到这个标记,就会把壁纸窗口衬在这个窗口之下。所以壁纸窗口的Z序与声明FLAG_SHOW_WALLPAPER标记的窗口息息相关。
声明FLAG_SHOW_WALLPAPER的窗口在WMS中被称为壁纸目标,存储在名为mWallpaperTarget的WindowState类型成员中。确定壁纸窗口Z序的核心工作就是寻找这个目标窗口。
旧版本API中:
这个目标窗口存在三种情况:
- 当前系统中只有一个窗口声明FLAG_SHOW_WALLPAPER标记,且是用户可见的,那么这个窗口就是目标窗口
- 当前系统中有多个窗口声明标记,此时Z序最高的窗口为目标窗口
- 当前系统有多个窗口声明标记,同时它们还在进行窗口动画,此时要做相对复杂的选择策略
确定壁纸目标的流程的原则是找到窗口列表中第一个声明FLAG_SHOW_WALLPAPER、并且对用户可见的窗口。如果候选窗口是旧的壁纸目标,且在动画过程中,那么就继续向下查找可能成为壁纸目标的窗口。
在这个过程中,候选的壁纸目标将会被保存到foundW中,其在窗口列表中的索引将会被保存到foundI中,这两个变量用于确定壁纸窗口的插入位置。
在下一个阶段中,如果新旧目标都在动画过程中,那么就要设置mLowerWallpaperTarget(Z序较小)和mUpperWallpaperTarget(Z序较大)。这个阶段主要是为了处理新旧目标都在动画过程中这种情况的。这个时候foundW和foundI会被指向Lower的目标(因为壁纸窗口最终是被衬在目标窗口的下面,肯定插入位置要在新的目标的下面,最终壁纸窗口会在foundI指向的目标的下面)。而当新的窗口目标在旧的窗口目标上面的时候(图中棕色块),真正的壁纸目标mWallpaperTarget可能会和壁纸窗口的实际位置发生分离(mWallpaperTarget指向Upper,foundI指向Lower),最终壁纸窗口的插入位置和壁纸目标之间会隔着一层Lower。不过这种情况很少,一般Lower和Upper都是空的,mWallpaperTarget会被设置为foundW。
Lower和Upper不为空的时候表示此时同时有两个用户可见的壁纸目标。但是壁纸窗口只有一个,此时WMS的策略为:
- 壁纸窗口的位置和可见性由Lower确定
- 壁纸的offset由mWallpaperTarget确定(有可能是Lower【绿色块】,也有可能是Upper【棕色块】)
- 壁纸的动画谁说了都不算,此时壁纸处于静止状态
然后就是确定壁纸窗口在窗口列表中的位置。这个过程有几条原则:
- Activity在执行动画的时候会对自己的Layer进行调整,壁纸窗口作为目标窗口的附属,也要对自己的layer进行调整。但是这只在Lower为null的时候才会执行,因为当不为null的时候,有两个目标都在进行动画,壁纸窗口无法知道使用哪一个目标的layer进行调整,所以这个时候壁纸窗口的layer不会动
- PhoneWindowManager设置了maxLayer,这是壁纸所能拥有的layer上限(不包含),这个上限与状态栏的layer相同,所以壁纸是不可能覆盖状态栏的
- 确保壁纸位于layer上限、目标窗口、目标窗口的父窗口以及STARTING窗口之下(这些窗口都紧邻在一起)
- 如果没有哪个窗口声明FLAG_SHOW_WALLPAPER标记,那么壁纸窗口就会被设置在壁纸列表的底部
确定完壁纸窗口的位置之后,就要将壁纸窗口移动到指定的位置。一般来说系统中只会存在一个壁纸窗口,但是在切换壁纸的时候,WallpaperManagerService.bindWallpaperComponentLocked的实现会首先启动新壁纸然后再销毁旧壁纸。这样,在一个较短的时间里,系统中可能存在两个不同的壁纸窗口。所以在调整壁纸窗口位置的时候会遍历WallpaperToken中的所有壁纸窗口,然后先将所有壁纸窗口从窗口列表中剥离,然后再按照顺序一个一个插入到foundI所指向的位置上(foundI的值随着插入递减),然后在销毁旧壁纸之后,新壁纸必然会在正确的位置上。
新版本API中寻找mWallpaperTarget的流程如下图:
首先调用的是DisplayContent的applySurfaceTransaction方法,在这个方法中会调用WallpaperController的adjustWallpaperWindows方法。
在这个方法中首先会判断当前窗口是不是处于FREEFORM模式,如果是的话,就会将壁纸设置为TopWallpaper(也就是将壁纸窗口本身设置为自己的Target)
Freeform Windows:允许应用在可以改变大小的窗口中运行,在Android Q中可以在开发者模式中打开(之前需要ADB或者第三方应用来完成)
然后就会调用DisplayContent的forWindows函数对所有窗口执行mFindWallpaperTargetFunction回调。在这个回调中会执行以下八个条件来寻找目标窗口:
寻找到合适的WallpaperTarget之后(或者没有找到),就会调用updateWallpaperWindowTarget来更新WallpaperTarget,在这里存在两种情况:
这其中使用到了两个WindowController的变量:
- mWallpaperTarget:如果不为空,这就是与壁纸相关的当前可见窗口
- mPrevWallpaperTarget:如果这变量不为空的话,我们当前就处于从一个壁纸目标动画转换到另一个壁纸目标的过程中,而这个变量保存的就是前一个壁纸目标。
旧版本的API中:
首先,壁纸可见的第一个条件就是存在一个壁纸目标(不存在的话就会将壁纸窗口放置到窗口列表的底部)。然后就会调用WMS.isWallpaperVisible来确定壁纸窗口是否可见,这里有三个条件,满足其中一个壁纸窗口就可见:
- Lower或者Upper不为null,这表明目前存在新旧两个壁纸目标正在执行动画
- wallPaperTarget(不是mWallpaperTarget,是foundW)没有被其他全屏窗口遮挡,即窗口部分或者全部可见
- wallpaperTarget属于一个Activity,而这个Activity正在执行动画。Activity执行动画的时候会有layer的调整,所以即使这个时候wallpaperTarget被全屏窗口遮挡,随着layer的调整,壁纸窗口也有可能显示在其他窗口之上,所以此时也要显示壁纸
isWallpaperVisible的结果会保存在壁纸窗口的WindowState.mWallpaperVisible中。这个设置有如下作用:
在动画系统中,WindowStateAnimator的prepareSurfaceLocked方法会检查这个变量的值,如果为false,就会调用WindowStateAnimator.hide,进而调用Surface.hide将壁纸隐藏。相反,会调用WindowStateAnimator.showSurfaceRobustlyLocked,进而调用Surface.show来使壁纸窗口重新可见
然后通过IWindow.dispatchAppVisibility方法将可见性传递给WallpaperService下的Engine对象。其中的mWindow成员将其解释为onVisibilityChanged,Engine根据这一回调重启或者终止画面的绘制。
所以壁纸的可见性有两层意义:
- WallpaperService中的Engine对象是否进行壁纸的绘制工作
- WMS中壁纸窗口的Surface隐藏还是可见
不同于常规窗口可见性变化时的创建或者销毁,壁纸窗口的可见性只会使Surface显示或者隐藏。这样壁纸窗口的可见性变化效率会非常高,但是其Surface的内存占用会一直都在。
新版本API中:
首先,壁纸可见的第一个条件就是存在一个壁纸目标(不存在的话就会将壁纸窗口放置到窗口列表的底部)。然后就会调用WallpaperController.isWallpaperVisible来确定壁纸窗口是否可见,这里有两个条件,满足其中一个壁纸窗口就可见:
isWallpaperVisible的结果保存的位置和作用和旧版本API一样,见上方。
同时在adjustWallpaperWindows方法中关注的壁纸窗口的可见性的点在于:窗口目前对于SurfaceFlinger是可见的,但是它对于用户而言是否是可见的呢?所以围绕窗口可见性与否对于窗口的偏移和位置做了调整:
首先在adjustWallpaperWindows里面设置留个参数:
这些参数会在之后的updateWallpaperOffset中计算出默认的壁纸偏移,然后调用WindowStateAniamtor的setWallpaperOffset设置。在这里x方向上的偏移会判断一次是否是RTL,使得这个行为适配大多数的桌面:
我们常用的习惯,称之为 LTR(Left-To-Right),其意为我们的阅读和书写习惯,是从左向右延伸的。而 RTL(Right-To-Left) 则正好相反,它的阅读和使用的习惯都是从右向左,常见使用 RTL 习惯的语言有阿拉伯语、希伯来语等。
设置完这些参数之后就会调用updateWallpaperTokens方法来更新每个壁纸窗口的WallpaperWindowToken。在这里首先调用的是WallpaperWindowToken中的updateWallpaperWindows方法。在这个方法里会做这些事情:
处理完窗口的可见性之后就会调用DisplayContent的assignWindowLayrers方法。在这里传递进去的参数是false,所以在这里将会禁用窗口的布局功能。在这里主要是进行两项工作:
调用assignChildLayers为子窗口调整Z-Order。在这个函数中会传入getPendingTransaction作为参数,将Layer的变化都保存到mPendingTransation中。其中会调整四种类型窗口的Layers:
处理完这些子窗口的Layer之后就要处理他们的子窗口的layer。在这里是为上方的Containers调用assignChildLayers方法,传入的参数都有Transaction,但是mAboveAppWindowContainers中还要考虑是否要传入mImeWindowContainers:
mAboveAppWindowContainers.assignChildLayers(t,needAssignIme == true ? mImeWindowsContainers : null)
调用scheduleAimation,这样就会开启动画系统,最终会调用到prepareSurfaces,其中会处理mPendingTransaction中积累的Layer变化的事件。这样就会将Z-Order的变化与Surface的显示与隐藏同步起来。
API29以前:
默认情况下,目标窗口发生动画的时候,壁纸窗口也会随着目标窗口产生同步的动画。效果实现在WindowStateAnimator.computeShownFrameLocked中。 这个方法有如下几个条件:
- LowerTarget为null,因为不为null的时候表示有两个壁纸目标在进行动画,这种情况下壁纸窗口无法确定使用哪一个目标的动画变化,所以壁纸窗口将保持不动
- 设置attachTransformation为壁纸目标窗口的动画变换,其前提是目标窗口动画的getDetachedWallpaper返回false。这表示壁纸窗口在动画过程中扮演目标窗口子窗口的作用。
- 设置appTransformation为目标窗口所属Activity的动画变换,前提是Activity的getDetachedWallpaper返回为false
默认情况下壁纸会随着目标窗口进行动画,除非目标窗口的Animation中设置了DetachWallpaper,或者新旧两个目标窗口都在进行动画。
之后就会将attachTransformation和appTransformation集成到tmpMatrix中,并由此计算出Surface最终的透明度、位置以及DsDx、DsDy、DtDx以及DtDy。
能够作为其他窗口背景的除了壁纸还有另外一种特殊的背景——Animation.setBackgroundColor设置一个背景色,目前Window Animator只会使用背景色的透明度分量,其他RGB会被忽略。但是如果指定了背景色的窗口是mWallpaperTarget、mLowerWallpaperTarget或者mUpperWallpaperTarget的话,这个Surface就会将壁纸窗口给遮挡起来。所以在WindowAnimator.updateWallpaperLocked中会进行检测,将Surface放在壁纸窗口的下面。
Android Q中Animation类的setDetachWallpaper方法已经被废弃,其注释说明:
@deprecated All Window animations are running with detached wallpaper
由此可以看到,现在所有的窗口动画都不会引起壁纸的动画,壁纸窗口不会随着目标窗口的动画而动画了!!
相对的,现在会为所有动画中的窗口设置AnimationBackground,这个过程如下图所示:
和以前的版本相同,在setAnimationBackground中同样只采用了Alpha分量。在Transaction类的setLayer方法中将mAinmationBackgroundSurface的Layer设置为了Interger.MIN_VALUE。这样这个Surface就不会遮挡其他窗口了。
Android壁纸使用了系统服务(管理者)+标准Android服务(实现者)的两层架构。当系统希望某些系统级的UI由第三方进行实现与拓展,同时又不希望给予第三方过多的操作窗口的权限时,这两层架构是最佳的选择。系统服务负责提供必要的系统级操作,而标准的Android服务可以在系统服务所规范的框架范围之内实现自由定制。