《深入理解Android 卷III》即将公布,作者是张大伟。

此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分。

在一个特别讲究颜值的时代。本书分析了Android 4.2中WindowManagerService、ViewRoot、Input系统、StatusBar、Wallpaper等重要“颜值绘制/处理”模块

第8章 深入理解Android壁纸(节选)

本章主要内容:

·  讨论动态壁纸的实现。

·  在动态壁纸的基础上讨论静态壁纸的实现。

·  讨论WMS对壁纸窗体所做的特殊处理。

本章涉及的源码文件名称及位置:

·  WallpaperManagerService.java

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

·  WallpaperService.java

frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

·  ImageWallpaper.java

frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

·  WallpaperManager.java

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

·  WindowManagerService.java

frameworks/base/services/java/com/android/server/wm/WindowManagerService.java

·  WindowStateAnimator.java

frameworks/base/services/java/com/android/server/wm/WindowStateAnimator.java

·  WindowAnimator.java

frameworks/base/services/java/com/android/server/wm/WindowAnimator.java

8.1 初识Android壁纸

本章将对壁纸的实现原理进行讨论。

在Android中,壁纸分为静态与动态两种。静态壁纸是一张图片,而动态壁纸则以动画为表现形式。或者能够对用户的操作作出反应。这两种形式看似差异非常大,事实上二者的本质是统一的。它们都以一个Service的形式执行在系统后台,并在一个类型为TYPE_WALLPAPER的窗体上绘制内容。进一步讲,静态壁纸是一种特殊的动态壁纸,它仅在窗体上渲染一张图片。并且不会对用户的操作作出反应。因此本章将首先通过动态壁纸的实现讨论Android壁纸的实现与管理原理,然后在对静态壁纸的实现做介绍。

Android壁纸的实现与管理分为三个层次:

·  WallpaperService与Engine。同SystemUI一样。壁纸执行在一个Android服务之中,这个服务的名字叫做WallpaperService。当用户选择了一个壁纸之后。此壁纸所相应的WallpaperService便会启动并開始进行壁纸的绘制工作,因此继承并定制WallpaperService是开发人员进行壁纸开发的第一步。

Engine是WallpaperService中的一个内部类,实现了壁纸窗体的创建以及Surface的维护工作。另外。Engine提供了可供子类重写的一系列回调,用于通知壁纸开发人员关于壁纸的生命周期、Surface状态的变化以及对用户的输入事件进行响应。能够说,Engine类是壁纸实现的核心所在。壁纸开发人员须要继承Engine类,并重写其提供的回调以完毕壁纸的开发。这一层次的内容主要体现了壁纸的实现原理。

·  WallpaperManagerService,这个系统服务用于管理壁纸的执行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口。当通过WallpaperManagaer的接口进行壁纸的切换时,WallpaperManagerService会取消当前壁纸的WallpaperService的绑定。并启动新壁纸的WallpaperService。另外,Engine类进行窗体创建时所使用的窗体令牌也是由WallpaperManagerService提供的。

这一层次主要体现了Android对壁纸的管理方式。

·  WindowManagerService,用于计算壁纸窗体的Z序、可见性以及为壁纸应用窗体动画。壁纸窗体(TYPE_WALLPAPER)的Z序计算不同于其它类型的窗体。其它窗体按照其类型会有固定的mBaseLayer以及mSubLayer,并结合它们所属的Activity的顺序或创建顺序进行Z序的计算,因此这些窗体的Z序相对固定。

而壁纸窗体则不然,它的Z序会依据FLAG_SHOW_WALLPAPER标记在其它窗体的LayoutParams.flags中的存在情况而不断地被调整。这一层次主要体现了Android对壁纸窗体的管理方式。

本章将通过对动态壁纸切换的过程进行分析揭示WallpaperService、Engine以及WallpaperManagerService三者的实现原理以及协作情况。静态壁纸作为动态壁纸的一种特殊情况,将会在完毕动态壁纸的学习之后于8.3节进行讨论。

而WindowManagerService对壁纸窗体的处理将在8.4节进行介绍。

8.2 深入理解动态壁纸

8.2.1 启动动态壁纸的方法

启动动态壁纸能够通过调用WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完毕。它接受一个ComponentName类型的參数。用于将希望启动的壁纸的WallpaperService的ComponentName告知WallpaperManagerService。

WallpaperManager.getIWallpaperManager()方法返回的是WallpaperManagerService的Bp端。

因此setWallpaperComponent()方法的实现位于WallpaperManagerService之中。參考事实上现:

[WallpaperManagerService.java-->WallpaperManagerService.setWallpaperComponent()]

public void setWallpaperComponent(ComponentNamename) {

// 设置动态壁纸须要调用者拥有一个签名级的系统权限

checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);

synchronized (mLock) {

/* ① 首先从mWallpaperMap中获取壁纸的执行信息WallpaperData。

WallpaperManagerService支持多用户机制,因此设备上的每个用户能够设置自己

的壁纸。

mWallpaperMap中为每个用户保存了一个WallpaperData实例,这个实例

中保存了和壁纸执行状态相关的信息。

比如WallpaperService的ComponentName。

到WallpaperService的ServiceConnection等。于是当发生用户切换时。

WallpaperManagerService能够从mWallpaperMap中获取新用户的WallpaperData,

并通过保存在当中的ComponentName又一次启动该用户所设置的壁纸。因此,

当通过setWallpaperComponent()设置新壁纸时。须要获取当前用户的WallpaperData,

并在随后更新其内容使之保存新壁纸的信息 */

intuserId = UserHandle.getCallingUserId();

WallpaperData wallpaper = mWallpaperMap.get(userId);

......

final long ident = Binder.clearCallingIdentity();

try{

......

// ② 启动新壁纸的WallpaperService

bindWallpaperComponentLocked(name, false, true, wallpaper, null);

}finally {

Binder.restoreCallingIdentity(ident);

}

}

}

注意 WallpaperManager.getIWallpaperManager()并没有作为SDK的一部分提供给开发人员。因此第三方应用程序是无法进行动态壁纸的设置的。

8.2.2 壁纸服务的启动原理

(1)壁纸服务的验证与启动

bindWallpaperComponentLocked()方法将会启动由ComponentName所指定的WallpaperService,并向WMS申请用于加入壁纸窗体的窗体令牌。只是在此之前,bindWallpaperComponentLocked()会对ComponentName所描写叙述的Service进行一系列的验证,以确保它是一个壁纸服务。而这一系列的验证过程体现了一个Android服务能够被当作壁纸必要的条件。

[WallpaperManagerService.java-->WallpaperManagerService.setWallpaperComponentLocked()]

boolean bindWallpaperComponentLocked(ComponentNamecomponentName, 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中,

开发人员能够通过改动这个值设置默认壁纸*/

String defaultComponent = mContext.getString(

com.android.internal.R.string.default_wallpaper_component);

if (defaultComponent != null) {

componentName = ComponentName.unflattenFromString(defaultComponent);

}

/* 倘若在上述的资源文件里没有指定一个默认壁纸,即default_wallpaper_component的

值被设置为@null),则使用ImageWallpaper取代默认壁纸。ImageWallpaper就是前文

所述的静态壁纸 */

if (componentName == null) {

componentName = IMAGE_WALLPAPER;

}

}

/* 接下来WallpaperMangerService会尝试从PackageManager中尝试获取ComponentName所

指定的Service的描写叙述信息,获取此信息的目的在于确认该Service是一个符合要求的壁纸服务 */

intserviceUserId = wallpaper.userId;

ServiceInfo si = mIPackageManager.getServiceInfo(componentName,

PackageManager.GET_META_DATA |

PackageManager.GET_PERMISSIONS,serviceUserId);

/* ① 第一个检查,要求这个Service必须声明其訪问权限为BIND_WALLPAPER。

这个签名级的系

统权限这是为了防止壁纸服务被第三方应用程序启动而产生混乱 */

if(!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {

if (fromUser) {

throw new SecurityException(msg);

}

return false;

}

WallpaperInfo wi = null;

/* ② 第二个检查,要求这个Service必须能够用来处理

           android.service.wallpaper.WallpaperService这个Action。

其检查方式是从PackageManager中查询全部能够处理

android.service.wallpaper.WallpaperService的服务,然后检查即将启动的服务

是否在PackageManager的查询结果之中 */

Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);

if(componentName != null && !componentName.equals(IMAGE_WALLPAPER)) {

// 获取全部能够处理android.service.wallpaper.WallpaperService的服务信息

List<ResolveInfo> ris =

mIPackageManager.queryIntentServices(intent,

intent.resolveTypeIfNeeded(mContext.getContentResolver()),

PackageManager.GET_META_DATA, serviceUserId);

/* ③ 第三个检查,要求这个Service必须在其meta-data中提供关于壁纸的描写叙述信息。

假设

即将启动的服务位于查询结果之中。便能够确定这是一个壁纸服务。此时会创建一

个WallpaperInfo的实例以解析并存储此壁纸服务的描写叙述信息。壁纸服务的描写叙述信息包括

了壁纸的开发人员、缩略图、简单的描写叙述文字以及用于对此壁纸进行參数设置的Activity的

名字等。

壁纸开发人员能够在AndroidManifest.xml中将一个包括了上述信息的xml文件设

置在名为android.service.wallpaper的meta-data中以提供这些信息 */

for (int i=0; i<ris.size(); i++) {

ServiceInfo rsi = ris.get(i).serviceInfo;

if (rsi.name.equals(si.name) &&

rsi.packageName.equals(si.packageName)){

try {

wi = newWallpaperInfo(mContext, ris.get(i));

} catch (XmlPullParserException e) {......}

break;

}

}

if (wi == null) {

/* wi为null表示即将启动的服务没有位于查询结果之中,或者没有提供必须的meta-data。

此时返回false表示绑定失败 */

return false;

}

}

......

}

......

}

可见WallpaperManagerService要求被启动的目标Service必须满足下面三个条件:

·  该服务必须要以android.permission.BIND_WALLPAPER作为其訪问权限。壁纸尽管是一个标准的Android服务,可是通过其它途径(如第三方应用程序)启动壁纸所在的服务是没有意义的。

因此Android要求作为壁纸的Service必须使用这个签名级的系统权限进行訪问限制,以免被意外的应用程序启动。

·  该服务必须被声明为能够处理android.service.wallpaper.WallpaperService这个Action。

WallpaperManagerService会使用这个Action对此服务进行绑定。

·  该服务必须在其AndroidManifest.xml中提供一个名为android.service.wallpaper的meta-data,用于提供动态壁纸的开发人员、缩略图与描写叙述文字。

一旦目标服务满足了上述条件,WallpaperManagerService就会着手进行目标服务的启动与绑定。

參考setWallpaperComponentLocked()方法的兴许代码:

[WallpaperManagerService.java-->WallpaperManagerService.setWallpaperComponentLocked()]

boolean bindWallpaperComponentLocked(ComponentNamecomponentName, boolean force,

boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {

...... // 检查服务是否符合要求的代码

/* ① 创建一个WallpaperConnection。它不仅实现了ServiceConnection接口用于监

听和WallpaperService之间的连接状态,同一时候还实现了IWallpaperConnection.Stub,

也就是说它支持跨进程通信。

在服务绑定成功后的WallpaperConnection.onServiceConnected()方法调用中,

WallpaperConnection的实例会被发送给WallpaperService。使其作为WallpaperService

向WallpaperManagerService进行通信的桥梁 */

WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);

// 为启动壁纸服务准备Intent

intent.setComponent(componentName);

intent.putExtra(Intent.EXTRA_CLIENT_LABEL,

com.android.internal.R.string.wallpaper_binding_label);

intent.putExtra(Intent.EXTRA_CLIENT_INTENT,PendingIntent.getActivityAsUser(

mContext, 0,

Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),

mContext.getText(com.android.internal.R.string.chooser_wallpaper)),

0, null, new UserHandle(serviceUserId)));

/* ② 启动制定的壁纸服务。

当服务启动完毕后,剩下的启动流程会在

WallpaperConnection.onServiceConnected()中继续 */

if(!mContext.bindService(intent,

newConn,Context.BIND_AUTO_CREATE, serviceUserId)) {

}

// ③ 新的的壁纸服务启动成功后。便通过detachWallpaperLocked()销毁旧有的壁纸服务

if(wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {

detachWallpaperLocked(mLastWallpaper);

}

// ④ 将新的壁纸服务的执行信息保存到WallpaperData中

wallpaper.wallpaperComponent = componentName;

wallpaper.connection = newConn;

/* 设置wallpaper.lastDiedTime。这个成员变量与其说描写叙述壁纸的死亡时间戳,不如说是

描写叙述其启动的时间戳。它用来在壁纸服务意外断开时(即壁纸服务非正常停止)检查此壁纸服务

的存活时间。当存活时间小于一个特定的时长时将会觉得这个壁纸的软件质量不可靠

从而选择使用默认壁纸。而不是重新启动这个壁纸服务 */

wallpaper.lastDiedTime = SystemClock.uptimeMillis();

newConn.mReply = reply;

/* ④ 最后向WMS申请注冊一个WALLPAPER类型的窗体令牌。

这个令牌会在onServiceConnected()

之后被传递给WallpaperService用于作为后者加入窗体的通行证 */

try{

if (wallpaper.userId == mCurrentUserId) {

mIWindowManager.addWindowToken(newConn.mToken,

WindowManager.LayoutParams.TYPE_WALLPAPER);

mLastWallpaper = wallpaper;

}

} catch (RemoteException e) {}

} catch(RemoteException e) {}

returntrue;

}

bindWallpaperComponentLocked()主要做了例如以下几件事情:

·  创建WallpaperConnection。因为实现了ServiceConnection接口。因此它将负责监听WallpaperManagerService与壁纸服务之间的连接状态。另外因为继承了IWallpaperConnection.Stub。因此它具有跨进程通信的能力。在壁纸服务绑定成功后,WallpaperConnection实例会被传递给壁纸服务作为壁纸服务与WallpaperManagerService进行通信的桥梁。

·  启动壁纸服务。通过Context.bindService()方法完毕。可见启动壁纸服务与启动一个普通的服务没有什么差别。

·  终止旧有的壁纸服务。

·  将属于当前壁纸的WallpaperConnection实例、componentName机器启动时间戳保存到WallpaperData中。

·  向WMS注冊WALLPAPER类型的窗体令牌。这个窗体令牌保存在WallpaperConnection.mToken中,并随着WallpaperConnection的创建而创建。

仅仅将指定的壁纸服务启动起来尚无法使得壁纸得以显示。因为新启动起来的壁纸服务因为没有可用的窗体令牌而导致其无法加入窗体。WallpaperManagerService必须通过某种方法将窗体令牌交给壁纸服务才行。

所以壁纸显示的后半部分的流程将在WallpaperConnection.onServiceConnected()回调中继续。

同其它服务一样,WallpaperManagerService会在这个回调之中获得一个Binder对象。

因此在进行onServiceConnected()方法的讨论之前。必须了解WallpaperManagerService在这个回调中将会得到一个什么样的Binder对象。

如今把分析目标转移到WallpaperService中。和普通服务一样。WallpaperService的启动也会经历onCreate()、onBind()这种生命周期回调。为了了解WallpaperManagerService能够从onServiceConnected()获取如何的Binder对象,须要看下WallpaperService.onBind()的实现:

[WallpaperService.java-->WallpaperService.onBind()]

public final IBinder onBind(Intent intent) {

/*onBind()新建了一个IWallpaperServiceWrapper实例。并将

其返回给WallpaperManagerService */

returnnew IWallpaperServiceWrapper(this);

}

IWallpaperServiceWrapper类继承自IWallpaperService.Stub。它保存了WallpaperService的实例,同一时候也实现了唯一的一个接口attach()。非常显然,当这个Binder对象返回给WallpaperManagerService之后,后者定会调用这个唯一的接口attach()以传递显示壁纸所必须的包括窗体令牌在内的一系列的參数。

(2)向壁纸服务传递创建窗体所需的信息

又一次回到WallpaperManagerService,当WallpaperService创建了IWallpaperServiceWrapper实例并返回后,WallpaperManagerService将会在WallpaperConnection.onServiceConnected()中收到回调。

參考事实上现:

[WallpaperManagerService.java-->WallpaperConnection.onServiceConnected()]

public void onServiceConnected(ComponentName name,IBinder service) {

synchronized (mLock) {

if (mWallpaper.connection == this) {

// 更新壁纸的启动时间戳

mWallpaper.lastDiedTime = SystemClock.uptimeMillis();

// ① 将WallpaperService传回的IWallpaperService接口保存为mService

mService = IWallpaperService.Stub.asInterface(service);

/* ② 绑定壁纸服务。attachServiceLocked()会调用IWallpaperService.attach()

方法以将壁纸服务创建窗体所需的信息传递过去 */

attachServiceLocked(this, mWallpaper);

// ③ 保存当前壁纸的执行状态到文件系统中,以便在系统重新启动或发生用户切换时能够恢复

saveSettingsLocked(mWallpaper);

}

}

}

进一步地,attachServiceLocked()方法会调用IWallpaperService.attach()方法,将创建壁纸窗体所需的信息发送给壁纸服务。

[WallpaperManagerService.java-->WallpaperManagerService.attachServiceCLocked()]

void attachServiceLocked(WallpaperConnection conn,WallpaperData wallpaper) {

try {

/* 调用IWallpaperService的唯一接口attach(),将创建壁纸窗体所须要的參数传递

给WallpaperService */

conn.mService.attach(conn, conn.mToken,

WindowManager.LayoutParams.TYPE_WALLPAPER, false,

wallpaper.width, wallpaper.height);

} catch(RemoteException e) {......}

}

attach()方法的參数非常多,它们的意义例如以下:

·  conn即WallpaperConnection,WallpaperService将通过它向WallpaperManagerService进行通信。WallpaperConnection继承自IWallpaperConnection,仅仅提供了两个接口的定义,即attachEngine()以及engineShown()。虽说WallpaperManager是WallpaperManagerService向外界提供的标准接口,可是这里仍然选择使用WallpaperConnection实现这两个接口的原因是因为attachEngine()以及engineShown()是仅仅有WallpaperService才须要用到并且是它与 WallpaperManagerService之间比較底层且私密的交流,将它们的实现放在通用的接口WallpaperManager中显然并不合适。这两个接口中比較重要的当属attachEngine()了。如前文所述,Engine类是实现壁纸的核心所在,而WallpaperService仅仅是一个用于承载壁纸的执行的容器而已。因此相对于WallpaperService。Engine是WallpaperManagerService更加关心的对象。所以当WallpaperService完毕了Engine对象的创建之后,就会通过attachEngine()方法将Engine对象的引用交给WallpaperManagerService。

·  conn.mToken就是在bindWallpaperComponent()方法中向WMS注冊过的窗体令牌。

是WallpaperService有权加入壁纸窗体的凭证。

·  WindowManager.LayoutParams.TYPE_WALLPAPER指明了WallpaperService须要加入TYPE_WALLPAPER类型的窗体。读者可能会质疑这个參数的意义:壁纸除了是TYPE_WALLPAPER类型以外难道还有其它的可能么?的确在实际的壁纸显示中WallpaperService必定须要使用TYPE_WALLPAPER类型加入窗体。可是有一个例外。即壁纸预览。

在LivePicker应用中选择一个动态壁纸时。首先会使得用户对选定的壁纸进行预览。

这一预览并非真的将壁纸设置给了WallpaperManagerService。而是LivePicker应用自行启动了相应的壁纸服务。并要求壁纸服务使用TYPE_APPLICATION_MEDIA_OVERLAY类型创建窗体。这样一来,壁纸服务所创建的窗体将会以子窗体的形式衬在LivePicker的窗体之下。从而实现了动态壁纸的预览。

·  false的參数名是isPreview. 用以指示启动壁纸服务的意图。当被实际用作壁纸时取值为false,而作为预览时则为true。仅当LivePicker对壁纸进行预览时才会使用true作为isPreview的取值。壁纸服务能够依据这一參数的取值对自己的行为作出调整。

当WallpaperManagerService向WallpaperService提供了用于创建壁纸窗体的足够的信息之后,WallpaperService便能够開始着手进行Engine对象的创建了。

(3)Engine的创建

调用IWallpaperService.attach()是WallpaperManagerService在壁纸服务启动后第一次与壁纸服务进行联系。

參考事实上现:

[WallpaperService.java-->IWallpaperServiceWrapper.attach()]

public void attach(IWallpaperConnection conn,IBinder windowToken,

intwindowType, boolean isPreview, int reqWidth, int reqHeight) {

// 使用WallpaperManagerService提供的參数构造一个IWallpaperEngineWarapper实例

newIWallpaperEngineWrapper(mTarget, conn, windowToken,

windowType, isPreview, reqWidth, reqHeight);

}

顾名思义,在attach()方法中所创建的IWallpaperEngineWrapper将会创建并封装Engine实例。IWallpaperEngineWrapper继承自IWallpaperEngine.Stub。因此它也支持跨Binder调用。

在随后的代码分析中可知。它将会被传递给WallpaperManagerService。作为WallpaperManagerService与Engine进行通信的桥梁。

另外须要注意的是,attach()方法的实现非常奇怪,它直接创建一个实例可是并没有将这个实例赋值给某一个成员变量。在attach()方法结束时岂不是会被垃圾回收?不难想到,在IWallpaperEngineWrapper的构造函数中一定有些动作能够使得这个实例不被释放。參考事实上现:

[WallpaperService.java-->IWallpaperEngineWrapper.IWallpaperEngineWrapper()]

IWallpaperEngineWrapper(WallpaperService context,

IWallpaperConnection conn, IBinder windowToken,

intwindowType, boolean isPreview, int reqWidth, int reqHeight) {

/* 创建一个HandlerCaller。

HandlerCaller是Handler的一个封装。而它与Handler的差别是额外提供了

一个executeOrSendMessage()方法。当开发人员在HandlerCaller所在的线程

执行此方法时会使得消息的处理函数立马得到执行。在其它线程中执行此方法的效果

则与Handler.sendMessage()别无二致。

除非阅读代码时遇到这种方法。读者

仅仅须要将其理解为Handler就可以。

注意意通过其构造函数的參数可知HandlerCaller保存了IWallpaperEngineWrapper的实例 */

mCaller= new HandlerCaller(context,

mCallbackLooper != null

? mCallbackLooper : context.getMainLooper(),

this);

// 将WallpaperManagerService所提供的參数保存下来

mConnection = conn; // conn即是WallpaperManagerService中的WallpaperConnection

mWindowToken = windowToken;

mWindowType = windowType;

mIsPreview = isPreview;

mReqWidth = reqWidth;

mReqHeight = reqHeight;

// 发送DO_ATTACH消息。兴许的流程转到DO_ATTACH消息的处理中进行

Messagemsg = mCaller.obtainMessage(DO_ATTACH);

mCaller.sendMessage(msg);

}

注意 在这里貌似并没有保存新建的IWallpaperEngineWrapper实例,它岂不是有可能在DO_ATTACH消息执行前就被Java的垃圾回收机制回收了?事实上不是这样。HandlerCaller的构造函数以及最后的sendMessage()操作使得这个IWallpaperEngineWrapper的实例得以坚持到DO_ATTACH消息能够得到处理的时刻。sendMessage()方法的调用使得Message被目标线程的MessageQueue引用,并且相应的Handler的被Message引用。而这个Handler是HandlerCaller的内部类,因此在Handler中有一个隐式的指向HandlerCaller的引用,最后在HandlerCaller中又存在着IWallpaperEngineWrapper的引用。因此IWallpaperEngineWrapper间接地被HandlerCaller所在线程的MessageQueue所引用着,因此在完毕DO_ATTACH消息的处理之前。IWallpaperEngineWrapper并不会被回收。

尽管这是建立在对Java引用以及Handler工作原理的深刻理解之上所完毕的精妙实现,可是它确实已经接近危急的边缘了。

在这里所创建的mCaller具有十分重要的地位。它是一个重要的线程调度器。全部壁纸相关的操作都会以消息的形式发送给mCaller,然后在IWallpaperEngineWrapper的executeMessage()方法中得到处理,从而这些操作转移到mCaller所在的线程上进行(如壁纸绘制、事件处理等)。

能够说mCaller的线程就是壁纸的工作线程。

默认情况下这个mCaller执行在壁纸服务的主线程上即context.getMainLooper()。只是当WallpaperService.mCallbackLooper不为null时会执行在mCallbackLooper所在的线程。mCaller执行在壁纸服务的主线程上听起来十分合理,然而提供手段以同意其执行在其它线程的做法却有些意外。事实上这是为了满足一种特殊的需求。以ImageWallper壁纸服务为例,它是SystemUI的一部分而SystemUI的主线程主要用来作为状态栏、导航栏的管理与绘制的场所,换句话说其主线程的工作已经比較繁重了。

因此ImageWallpaper能够通过这一手段将壁纸的工作转移到另外一个线程中进行。只是因为这一机制可能带来同步上的问题,因此在Android 4.4及兴许版本号中被废除了。

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

[WallpaperService.java-->IWallpaperEngineWrapper.executeMessage()]

public void executeMessage(Message message) {

switch(message.what) {

caseDO_ATTACH: {

try {

/* ① 把IWallpaperEngineWrapper实例传递给WallpaperConnection进行保存。

至此这个实例便名花有主,再也不用操心被回收了,并且WallpaperManagerService

还能够通过它与实际的Engine进行通信 */

mConnection.attachEngine(this);

} catch (RemoteException e) {}

/* ② 通过onCreateEngine()方法创建一个Engine。

onCreateEngine()是定义在WallpaperService中的一个抽象方法。

WallpaperService的实现者须要依据自己的须要返回一个自己定义的Engine的子类 */

Engine engine = onCreateEngine();

mEngine = engine;

/* ③ 将新建的Engine加入到WallpaperService.mActiveEngines列表中。

读者可能会比較奇怪。为什么是列表?难道一个Wallpaper可能会有多个Engine么?

这个奇怪之处还是壁纸预览所引入的。当壁纸A已经被设置为当前壁纸之时,系统中会存

在一个它所相应的WallpaperService,以及在其内部会存在一个Engine。

此时当LivePicker或其它壁纸管理工具预览壁纸A时,它所相应的WallpaperService

仍然仅仅有一个。可是在其内部会变成两个Engine。

这一现象更能说明,WallpaperService仅仅是提供壁纸执行的场所,而Engine才是真正

的壁纸的实现 */

mActiveEngines.add(engine);

// ④ 最后engine.attach()将会完毕窗体的创建、第一帧的绘制等工作

engine.attach(this);

return;

}

}

}

正如前文所述。作为拥有跨Binder调用的IWallpaperEngineWrapper通过attachEngine()方法将自己传递给了WallpaperConnection。后者将其保存在WallpaperConnection.mEngine成员之中。

从此之后,WallpaperManagerService便能够通过WallpaperConnection.mEngine与壁纸服务进程中的IWallpaperEngineWrapper进行通信,而IWallpaperEngineWrapper进一步将来自WallpaperManagerService中的请求或设置转发给Engine对象,从而实现了WallpaperManagerService对壁纸的控制。

到眼下为止。WallpaperManagerService与壁纸服务之间已经出现了三个用于跨Binder通信的对象。它们各自是:

·  IWallpaperService。实如今壁纸服务进程之中,它所提供的唯一的方法attach()用于在壁纸服务启动后接收窗体创建所需的信息。或者说为了完毕壁纸的初始化工作。除此之外IWallpaperService不负责不论什么功能,WallpaperManagerService对壁纸进行的请求与设置都交由在attach()的过程中所创建的IWallpaperEngineWrapper实例完毕。

·  WallpaperConnection。实如今WallpaperManagerService中。并通过IWallpaperService.attach()方法传递给了IWallpaperEngineWrapper。壁纸服务通过WallpaperConnection的attachEngine()方法将IWallpaperEngineWrapper实例传递给WallpaperManagerService进行保存。另外壁纸服务还通过它的engineShown()方法将壁纸显示完毕的事件通知给WallpaperManagerService。

·  IWallpaperEngineWrapper。实如今壁纸进程中。Engine实例是壁纸实现的核心所在。

作为Engine实例的封装者。它是WallpaperManagerService对Engine进行请求或设置的唯一接口。

整体来说,IWallpaperService与WallpaperConnection主要服务于壁纸的创建阶段。而IWallpaperEngineWrapper则用于在壁纸的执行阶段对Engine进行操作与设置。

说明 按照常规的思想来判断。WallpaperManagerService与WallpaperService之间应该仅仅须要IWallpaperService提供接口对壁纸进行操作与设置。为什么要添加一个IWallpaperEngineWrapper呢?这得从WallpaperService与Engine之间的关系说起。

IWallpaperService在WallpaperManagerService看来表示的是WallpaperService,而IWallpaperEngineWrapper则表示的是Engine。WallpaperService是Engine执行的容器。因此它所提供的唯一的方法attach()用来在WallpaperService中创建新的Engine实例(由创建一个IWallpaperEngineWrapper实例来完毕)。

Engine则是壁纸的详细实现。因此IWallpaperEngineWrapper所提供的方法用来对壁纸进行操作与设置。从这个意义上来讲IWallpaperService与IWallpaperEngineWrapper的同一时候存在是合理的。另外,将IWallpaperService与IWallpaperEngineWrapper分开还有着简化实现的意义。从DO_ATTACH消息的处理过程可知,WallpaperService中能够同一时候执行多个Engine实例。

而WallpaperManagerService或LivePicker所关心的仅仅是某一个Engine,而不是WallpaperService中的全部Engine。因此相对于使用IWallpaperService的接口时必须在參数中指明所须要操作的Engine,直接操作IWallpaperEngineWrapper更加简洁直接。

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

參考其代码:

[WallpaperService.java-->Engine.attach()]

void attach(IWallpaperEngineWrapper wrapper) {

......

// 保存必要的信息

mIWallpaperEngine = wrapper;

mCaller= wrapper.mCaller;

mConnection = wrapper.mConnection;

mWindowToken = wrapper.mWindowToken;

/* ① mSurfaceHolder是一个BaseSurfaceHolder类型的内部类的实例。

Engine对其进行了简单的定制。开发人员能够通过mSurfaceHolder定制所须要的Surface类型 */

mSurfaceHolder.setSizeFromLayout();

mInitializing = true;

// 获取WindowSession,用于与WMS进行通信

mSession= WindowManagerGlobal.getWindowSession(getMainLooper());

//mWindow是IWindow的实现,窗体创建之后它将用于接收来自WMS的回调

mWindow.setSession(mSession);

//Engine须要监听屏幕状态。这是为了保证在屏幕关闭之后,动态壁纸能够停止动画的渲染以节省电量

mScreenOn =

((PowerManager)getSystemService(Context.POWER_SERVICE)).isScreenOn();

IntentFilter filter = new IntentFilter();

filter.addAction(Intent.ACTION_SCREEN_ON);

filter.addAction(Intent.ACTION_SCREEN_OFF);

registerReceiver(mReceiver, filter);

/* ② 调用Engine.onCreate()。

Engine的子类往往须要重写此方法以改动mSurfaceHolder的属性,如像素格式。尺寸等。

注意此时尚未创建窗体。在这里所设置的SurfaceHolder的属性将会在创建窗体时生效 */

onCreate(mSurfaceHolder);

mInitializing = false;

mReportedVisible = false;

/* ③ 最后updateSurface将会依据SurfaceHolder的属性创建窗体以及Surface。并进行

      壁纸的第一次绘制 */

updateSurface(false, false, false);

}

Engine.attach()方法执行的结束标志着壁纸启动工作的完毕。至此在最后的updateSurface()方法结束之后新的壁纸便显示出来了。

(4)壁纸的创建流程

可见,壁纸的创建过程比較复杂。在这个过程中存在着多个Binder对象之间的互相调用。

因此有必要对此过程进行一个简单的整理:

·  首先,壁纸管理程序(如LivePicker)调用IWallpaperManager.setWallpaperComponent()要求WallpaperManagerService设置指定的壁纸

·  WallpaperManagerService通过调用bindWallpaperComponentLocked()将给定的壁纸服务启动起来。同一时候旧有的壁纸服务会被终止。

·  WallpaperManagerService成功连接壁纸服务后,调用壁纸服务的attach()方法将窗体令牌等參数交给壁纸服务。

·  壁纸服务响应attach()的调用,创建一个Engine。

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

而在这个过程中,WallpaperManagerService中存在例如以下重要的数据结构:

·  WallpaperInfo,存储了动态壁纸的开发人员、缩略图与描写叙述信息。这个数据结构创建于WallpaperManagerService.bindWallpaperComponentLocked()方法,其内容来自于壁纸所在应用程序的AndroidManifest.xml中名为android.service.wallpaper的meta-data。

·  WallpaperConnection,它不仅仅是壁纸服务与WallpaperManagerService进行通信的渠道,它同一时候也保存了与壁纸服务相关的重要的执行时信息。如IWallpaperService、IWallpaperEngineWrapper、WallpaperInfo以及用于创建窗体所需的窗体令牌。WallpaperConnection创建于WallpaperManagerService.bindWallpaperComponentLocked()方法。

·  WallpaperData,它保存了一个壁纸在WallpaperManagerService中可能用到的全部信息。包括壁纸服务的ComponentName,WallpaperConnection,壁纸服务的启动时间等。WallpaperData被保存在一个名为mWallpaperMap的SparseArray中,并且设备中每个用户都会拥有一个固定的WallpaperData实例。当前用户进行壁纸切换时会更新WallpaperData的内容,而不是新建一个WallpaperData实例。另外,WallpaperData中还保存了与静态壁纸相关的一些信息。关于静态壁纸的内容将在8.3节进行介绍。

壁纸的创建过程同一时候体现了壁纸服务与WallpaperManagerService之间的关系。如图8-1所看到的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

图 8 - 1 壁纸服务与WallpaperManagerService之间的关系

《深入理解Android 卷III》第八章深入理解Android壁纸的更多相关文章

  1. 《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue

    <深入理解Android 卷III>即将公布.作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分. ...

  2. 《深入理解Android 卷III》第七章 深入理解SystemUI

    <深入理解Android 卷III>即将公布,作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分. ...

  3. 《深入理解Android 卷III》第四章 深入理解WindowManagerService

    <深入理解Android 卷III>即将公布,作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白.即Android Framework中和UI相关的部分. ...

  4. 《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系统

    <深入理解Android 卷III>即将公布,作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分. ...

  5. 《深入理解Android 卷III》第五章 深入理解Android输入系统

    <深入理解Android 卷III>即将公布.作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白.即Android Framework中和UI相关的部分. ...

  6. [深入理解Android卷一全文-第八章]深入理解Surface系统

    由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版.而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...

  7. 安卓学习资料推荐《深入理解Android:卷2》下载

    下载地址:百度云下载地址 编辑推荐 <深入理解Android:卷2>编辑推荐:经典畅销书<深入理解Android:卷I>姊妹篇,51CTO移动开发频道和开源中国社区一致鼎力推荐 ...

  8. Android 多媒体视频播放一( 多媒体理解与经验分享)

    前言 说到android的多媒体,一把辛酸一把泪,当初听说会多媒体的比较牛掰,公司也有需求,于是乎我也积极的加入研究android多媒体的行列,记得以前刚接触的时候,最开始还是比较头大的,主要是但是很 ...

  9. Android图片加载库的理解

    前言     这是“基础自测”系列的第三篇文章,以Android开发需要熟悉的20个技术点为切入点,本篇重点讲讲Android中的ImageLoader这个库的一些理解,在Android上最让人头疼是 ...

随机推荐

  1. Tool:ProcessOn

    ylbtech-Tool:ProcessOn ProcessOn是一个面向垂直专业领域的作图工具和社交网络,成立于2011年6月并于2012年启动.ProcessOn将全球的专家顾问.咨询机构.BPM ...

  2. mybatis的二级缓存

    在mybatis主配置文件里configuration标签里添加 <settings> <setting name="cacheEnabled" value=&q ...

  3. HTML中javascript使用dom获取dom节点范例

    <!-- HTML结构 --> <div id="test-div"> <div class="c-red"> <p ...

  4. office 2010 破解

    使用Rearm命令激活延迟重置Office 20101.安装Offcie 2010 安装Offcie 2010,默认30天的试用期,这里要注意,上文提供的Office 2010是零售版,所以没有序列号 ...

  5. java的封箱和拆箱

    1.基本概念 字节的单位:byte.位的单位:bit,1byte=8bit 2.8种基本数据类型 4种整型,2种浮点类型,1种用于表示Unicode编码的字符单元的字符类型和1种用于表示真值的bool ...

  6. node函数buf.readDoubleBE详解

    offset {Number} 0 noAssert {Boolean} 默认:false 返回:{Number} 从该 Buffer 指定的带有特定尾数格式(readDoubleBE() 返回一个较 ...

  7. dubbo之多协议

    (1) 不同服务不同协议 比如:不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议 consumer.xml <?xml version="1.0& ...

  8. iOS https 证书链获取

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)chall ...

  9. mac 上执行 rm -rf /

    # 很可怕的指令,清空磁盘所有资料,千万不要用 sudo 尝试,吓的小心肝差掉跳出来 rm -rf / 无聊,想执行rm -rf /会怎样,想起没加sudo时对~/download执行提示权限不足,被 ...

  10. 移动端mui常用方法

    本文分享一些用Mui的时候所采的坑 1.mui中上拉刷新事件a标签中的链接.元素onclick事件在手机上点击不了 mui('body').on('tap','a',function(){docume ...