一、什么是Android壁纸?
Android中,壁纸分为动态壁纸和静态壁纸两种。静态壁纸是一张图片,动态壁纸是以动画为表现形式,有的可以对用户的操作作出反应。二者表现形式看似差异很大,但是二者的本质是统一的: 它们都以一个Service的形式运行在系统后台,并在一个类型为TYPE_WALLPAPER的窗口上绘制内容。 实质上,静态壁纸是一种特殊的动态壁纸。
Android壁纸管理的三个层次:
- WallpaperService和Engine(壁纸的实现原理层次):壁纸的绘制由WallpaperService控制,继承和定制WallpaperEngine是进行壁纸开发的第一步。Engine是WallpaperService的一个内部类,实现了壁纸窗口的创建以及Surface的维护工作。同时Engine还提供了可供子类重写的一系列回调,用于通知开发者壁纸的生命周期、Surface状态变化以及对用户的输入事件作出回应。
- WallpaperManagerService(对壁纸的管理方式层次):用于管理壁纸的运行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口。切换壁纸时会取消当前壁纸的WallpaperService的绑定,然后启动新的WallpaperService。Engine类进行窗口创建的时候使用的窗口令牌也是由WallpaperManagerService提供。
- WindowManagerService(对壁纸窗口的管理形式):用于计算壁纸窗口的Z轴排序、可见性以及为壁纸应用动画。壁纸窗口的Z序会根据FLAG_SHOW_WALLPAPER标记在其他窗口的LayoutParams.flags中的存在情况而不断调整,相对于其他窗口来说较为不稳定。
二、动态壁纸
1.动态壁纸的启动流程
启动动态壁纸可以通过调用WallpaperManagerService.setWallpaperComponent()方法来完成,这个方法的步骤如下:
1)首先调用mWallpaperMap.get(UserId)来获取壁纸的运行信息。
Q:为什么需要根据UserID来划分壁纸的运行信息?
A:WallpaperManagerService支持多用户机制,设备上的每个用户都可以设置自己的壁纸。mWallpaperMap为每个用户保存了一个WallpaperData实例,其中保存了和壁纸状态相关的运行信息:WallpaperService的ComponentName、ServiceConnection等。当发生用户切换的时候,获取到相应用户的WallpaperData,然后获取ComponentName,这样就可以重新启动用户的壁纸。
2)调用bindWallpaperComponentLocked方法,启动新壁纸的WallpaperService。
a、认证服务资格
这个过程首先会对服务进行一系列认证,确认是一个壁纸服务之后才会启动WallpaperService,检查的内容如下:
- 服务必须以android.permission.BIND_WALLPAPER作为其访问权限。这个访问权限是一个签名级的访问权限,以免该服务被意外的应用程序启动
- 服务必须被声明可以处理android.service.wallpaper.WallpaperService这个Action,因为WallpaperManagerService会使用这个Action对这个服务进行绑定
- 服务必须在AndroidManifest.xml中提供android.service.wallpaper的meta-data,用来提供动态壁纸的开发者、缩略图以及描述文字
服务满足条件之后,就会着手启动目标服务并绑定,步骤如下:
-
创建WallpaperConnection,其实现了ServiceConnection接口用于监听和WallpaperService之间的连接状态,同时还实现了IWallpaperConnection.stub,支持跨进程通信。服务绑定成功之后,会在onServiceConnected调用中被发送给WallpaperService,成为WallpaperService和WallpaperManagerService之间通信的桥梁。
-
调用mContext.bindServiceAsUser启动指定的服务。之后的流程会在WallpaperConnection.onServiceConnected回调中完成。
-
新的壁纸服务启动之后,销毁旧的壁纸服务
-
将新的壁纸服务的信息保存到WallpaperData中
在WallpaperData中会有一个lastDiedTime属性,描述壁纸服务的存活时间,如果小于一定的数值就会认为这个壁纸服务不可靠从而选择默认壁纸。
-
向WindowManagerService申请注册一个WALLPAPER类型的窗口令牌,其会再onServiceConnected之后被传递给WallpaperService作为添加窗口的令牌
b、传递创建窗口所需信息
仅仅将指定的壁纸服务启动起来还不能让壁纸显示出来,因为还没有窗口令牌而无法添加窗口。所以这后半部流程会在WallpaperConnection的onServiceConnected方法回调中进行。
在WallpaperService的onBind方法中会返回一个IWallpaperServiceWrapper实例。这个类继承了IWallpaperService.stub。保存了Wallpaper的实例,同时也实现了唯一的一个接口attach()。
WallpaperManagerService会在WallpaperConnection.onServiceConnected方法中收到回调,然后会进行以下三步:
- 将WallpaperService传回的IWallpaperService接口保存为mService
- 绑定壁纸服务,调用attachServiceLocked方法,这个方法会调用IWallpaperService.attach方法来传递壁纸服务创建窗口的信息
- saveSettingLocked,保存壁纸运行状态到文件系统中
其中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
c、创建Engine
调用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,这之后就不