《深入理解Android 卷III》第八章深入理解Android壁纸

版权声明:From 阿拉神农 https://blog.csdn.net/Innost/article/details/47660645

《深入理解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所示。


图 8 - 1 壁纸服务与WallpaperManagerService之间的关系
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页