# 第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之間的關系