#### 1.SampleWindow的實現
在這一節里將編寫一個最簡單的Java程序SampleWindow,僅使用WMS的接口創建并渲染一個動畫窗口。此程序將拋開Activity、Wallpaper等UI架構的復雜性,直接了當地揭示WMS的客戶端如何申請、渲染并注銷自己的窗口。同時這也初步地反應了WMS的工作方式。
這個例子很簡單,只有三個文件:
- SampleWindow.java 主程序源代碼。
- Android.mk 編譯腳本。
- sw.sh 啟動器。
分別看一下這三個文件的實現:
**SampleWindow.java::SampleWindow**
```
package understanding.wms.samplewindow;
......
public class SampleWindow {
publicstatic void main(String[] args) {
try {
//SampleWindow.Run()是這個程序的主入口
new SampleWindow().Run();
} catch (Exception e) {
e.printStackTrace();
}
}
//IWindowSession 是客戶端向WMS請求窗口操作的中間代理,并且是進程唯一的
IWindowSession mSession = null;
//InputChannel 是窗口接收用戶輸入事件的管道。在第5章中將對其進行詳細的探討
InputChannel mInputChannel = new InputChannel();
// 下面的三個Rect保存了窗口的布局結果。其中mFrame表示了窗口在屏幕上的位置與尺寸
// 在4.4中將詳細介紹它們的作用以及計算原理
RectmInsets = new Rect();
RectmFrame = new Rect();
RectmVisibleInsets = new Rect();
Configuration mConfig = new Configuration();
// 窗口的Surface,在此Surface上進行的繪制都將在此窗口上顯示出來
SurfacemSurface = new Surface();
// 用于在窗口上進行繪圖的畫刷
PaintmPaint = new Paint();
// 添加窗口所需的令牌,在4.2節將會對其進行介紹
IBindermToken = new Binder();
// 一個窗口對象,本例演示了如何將此窗口添加到WMS中,并在其上進行繪制操作
MyWindowmWindow = new MyWindow();
//WindowManager.LayoutParams定義了窗口的布局屬性,包括位置、尺寸以及窗口類型等
LayoutParams mLp = new LayoutParams();
Choreographer mChoreographer = null;
//InputHandler 用于從InputChannel接收按鍵事件做出響應
InputHandler mInputHandler = null;
booleanmContinueAnime = true;
publicvoid Run() throws Exception{
Looper.prepare();
// 獲取WMS服務
IWindowManager wms = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
// 通過WindowManagerGlobal獲取進程唯一的IWindowSession實例。它將用于向WMS
// 發送請求。注意這個函數在較早的Android版本(如4.1)位于ViewRootImpl類中
mSession= WindowManagerGlobal.getWindowSession(Looper.myLooper());
// 獲取屏幕分辨率
IDisplayManager dm = IDisplayManager.Stub.asInterface(
ServiceManager.getService(Context.DISPLAY_SERVICE));
DisplayInfo di = dm.getDisplayInfo(Display.DEFAULT_DISPLAY);
Point scrnSize = new Point(di.appWidth, di.appHeight);
// 初始化WindowManager.LayoutParams
initLayoutParams(scrnSize);
// 將新窗口添加到WMS
installWindow(wms);
// 初始化Choreographer的實例,此實例為線程唯一。這個類的用法與Handler
// 類似,不過它總是在VSYC同步時回調,所以比Handler更適合做動畫的循環器[1]
mChoreographer= Choreographer.getInstance();
// 開始處理第一幀的動畫
scheduleNextFrame();
// 當前線程陷入消息循環,直到Looper.quit()
Looper.loop();
// 標記不要繼續繪制動畫幀
mContinueAnime= false;
// 卸載當前Window
uninstallWindow(wms);
}
publicvoid initLayoutParams(Point screenSize) {
// 標記即將安裝的窗口類型為SYSTEM_ALERT,這將使得窗口的ZOrder順序比較靠前
mLp.type = LayoutParams.TYPE_SYSTEM_ALERT;
mLp.setTitle("SampleWindow");
// 設定窗口的左上角坐標以及高度和寬度
mLp.gravity = Gravity.LEFT | Gravity.TOP;
mLp.x = screenSize.x / 4;
mLp.y = screenSize.y / 4;
mLp.width = screenSize.x / 2;
mLp.height = screenSize.y / 2;
// 和輸入事件相關的Flag,希望當輸入事件發生在此窗口之外時,其他窗口也可以接受輸入事件
mLp.flags = mLp.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
publicvoid installWindow(IWindowManager wms) throws Exception {
// 首先向WMS聲明一個Token,任何一個Window都需要隸屬與一個特定類型的Token
wms.addWindowToken(mToken,WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
// 設置窗口所隸屬的Token
mLp.token = mToken;
// 通過IWindowSession將窗口安裝進WMS,注意,此時僅僅是安裝到WMS,本例的Window
// 目前仍然沒有有效的Surface。不過,經過這個調用后,mInputChannel已經可以用來接受
// 輸入事件了
mSession.add(mWindow,0, mLp, View.VISIBLE, mInsets, mInputChannel);
/*通過IWindowSession要求WMS對本窗口進行重新布局,經過這個操作后,WMS將會為窗口
創建一塊用于繪制的Surface并保存在參數mSurface中。同時,這個Surface被WMS放置在
LayoutParams所指定的位置上 */
mSession.relayout(mWindow,0, mLp, mLp.width, mLp.height, View.VISIBLE,
0, mFrame, mInsets,mVisibleInsets, mConfig, mSurface);
if(!mSurface.isValid()) {
thrownew RuntimeException("Failed creating Surface.");
}
// 基于WMS返回的InputChannel創建一個Handler,用于監聽輸入事件
//mInputHandler一旦被創建,就已經在監聽輸入事件了
mInputHandler= new InputHandler(mInputChannel, Looper.myLooper());
}
publicvoid uninstallWindow(IWindowManager wms) throws Exception {
// 從WMS處卸載窗口
mSession.remove(mWindow);
// 從WMS處移除之前添加的Token
wms.removeWindowToken(mToken);
}
publicvoid scheduleNextFrame() {
// 要求在顯示系統刷新下一幀時回調mFrameRender,注意,只回調一次
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION
, mFrameRender, null);
}
// 這個Runnable對象用以在窗口上描繪一幀
publicRunnable mFrameRender = new Runnable() {
@Override
publicvoid run() {
try{
// 獲取當期時間戳
long time = mChoreographer.getFrameTime() % 1000;
// 繪圖
if (mSurface.isValid()) {
Canvas canvas = mSurface.lockCanvas(null);
canvas.drawColor(Color.DKGRAY);
canvas.drawRect(2 * mLp.width * time / 1000
- mLp.width, 0, 2 *mLp.width * time
/ 1000, mLp.height,mPaint);
mSurface.unlockCanvasAndPost(canvas);
mSession.finishDrawing(mWindow);
}
if(mContinueAnime)
scheduleNextFrame();
} catch (Exception e) {
e.printStackTrace();
}
}
};
// 定義一個類繼承InputEventReceiver,用以在其onInputEvent()函數中接收窗口的輸入事件
classInputHandler extends InputEventReceiver {
Looper mLooper = null;
publicInputHandler(InputChannel inputChannel, Looper looper) {
super(inputChannel,looper);
mLooper= looper;
}
@Override
publicvoid onInputEvent(InputEvent event) {
if(event instanceof MotionEvent) {
MotionEvent me = (MotionEvent)event;
if (me.getAction() ==MotionEvent.ACTION_UP) {
// 退出程序
mLooper.quit();
}
}
super.onInputEvent(event);
}
}
// 實現一個繼承自IWindow.Stub的類MyWindow。
classMyWindow extends IWindow.Stub {
// 保持默認的實現即可
}
}
```
由于此程序使用了大量的隱藏API(即SDK中沒有定義這些API),因此需要放在Android源碼環境中進行編譯它。對應的Android.mk如下:
**Android.mk**
```
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := samplewindow
include $(BUILD_JAVA_LIBRARY)
```
將這兩個文件放在$TOP/frameworks/base/cmds/samplewindow/下,然后用make或mm命令進行編譯。最終生成的結果是samplewindow.jar,文件位置在out/target/<ProductName>/system/framework/下。將該文件通過adb push到手機的/system/framework/下。
>[info] **提示**:讀者可使用Android4.2模擬器來運行此程序。
然而,samplewindow.jar不是一個可執行程序,。故,需借助Android的app\_process工具來加載并執行它。筆者編寫了一個腳本做為啟動器:
**sw.sh**
```
base=/system
export CLASSPATH=$base/framework/samplewindow.jar
exec app_process $base/binunderstanding.wms.samplewindow.SampleWindow "$@"
```
**注意** app\_process其實就是大名鼎鼎的zygote。不過,只有使用--zygote參數啟動時它才會給改名為zygote\[2\],否則就像java –jar命令一樣,運行指定類的main靜態函數。
在手機中執行該腳本,其運行結果是一個灰色的方塊不斷地從屏幕左側移動到右側,如圖4-1所示。
:-: 
圖 4-1 SampleWindow在手機中的運行效果
#### 2.初識窗口的創建、繪制與銷毀
SampleWindow的這段代碼雖然簡單,但是卻很好地提煉了一個窗口的創建、繪制以及銷毀的過程。注意,本例沒有使用任何 WMS以外的系統服務,也沒有使用Android系統四大組件的框架,也就是說,如果你愿意,可以利用WMS實現自己的UI與應用程序框架,這樣就可以衍生出一個新的平臺了。
總結在客戶端創建一個窗口的步驟:
- 獲取IWindowSession和WMS實例。客戶端可以通過IWindowSession向WMS發送請求。
- 創建并初始化WindowManager.LayoutParams。注意這里是WindowManager下的LayoutParams,它繼承自ViewGroup.LayoutParams類,并擴展了一些窗口相關的屬性。其中最重要的是type屬性。這個屬性描述了窗口的類型,而窗口類型正是WMS對多個窗口進行ZOrder排序的依據。
- 向WMS添加一個窗口令牌(WindowToken)。本章后續將分析窗口令牌的概念,目前讀者只要知道,窗口令牌描述了一個顯示行為,并且WMS要求每一個窗口必須隸屬于某一個顯示令牌。
- 向WMS添加一個窗口。必須在LayoutParams中指明此窗口所隸屬于的窗口令牌,否則在某些情況下添加操作會失敗。在SampleWindow中,不設置令牌也可成功?完成添加操作,因為窗口的類型被設為TYPE\_SYSTEM\_ALERT,它是系統窗口的一種。而對于系統窗口,WMS會自動為其創建顯示令牌,故無需客戶端操心。此話題將會在后文進行更具體的討論。
- 向WMS申請對窗口進行重新布局(relayout)。所謂的重新布局,就是根據窗口新的屬性去調整其Surface相關的屬性,或者重新創建一個Surface(例如窗口尺寸變化導致之前的Surface不滿足要求)。向WMS添加一個窗口之后,其僅僅是將它在WMS中進行了注冊而已。只有經過重新布局之后,窗口才擁有WMS為其分配的畫布。有了畫布,窗口之后就可以隨時進行繪制工作了。
而窗口的繪制過程如下:
- 通過Surface.lock()函數獲取可以在其上作畫的Canvas實例。
- 使用Canvas實例進行作畫。
- 通過Surface.unlockCanvasAndPost()函數提交繪制結果。
* * * * *
**提示** 關于Surface的原理與使用方法,請參考《卷 I》第8章“深入理解Surface系統”。
* * * * *
這是對Surface作畫的標準方法。在客戶端也可以通過OpenGL進行作畫,不過這超出了本書的討論范圍。另外,在SampleWindow例子中使用了Choreographer類進行了動畫幀的安排。Choreographer意為編舞指導,是Jelly Bean新增的一個工具類。其用法與Handler的post()函數非Z且不會再顯示新的窗口,則需要從WMS將之前添加的顯示令牌一并刪除。
#### 3.窗口的概念
在SampleWindow例子中,有一個名為mWindow(類型為IWindow)的變量。讀者可能會理所當然地認為它就是窗口了。其實這種認識并不完全正確。IWindow繼承自Binder,并且其Bn端位于應用程序一側(在例子中IWindow的實現類MyWindow就繼承自IWindow.Stub),于是其在WMS一側只能作為一個回調,以及起到窗口Id的作用。
那么,窗口的本質是什么呢?
是進行繪制所使用的畫布:Surface。
當一塊Surface顯示在屏幕上時,就是用戶所看到的窗口了。客戶端向WMS添加一個窗口的過程,其實就是WMS為其分配一塊Surface的過程,一塊塊Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈現出多姿多彩的界面來。所以從這個意義上來講,WindowManagerService被稱之為SurfaceManagerService也說得通的。
于是,根據對Surface的操作類型可以將Android的顯示系統分為三個層次,如圖4-2所示。
:-: 
圖 4-2 Android顯示系統的三個層次
在圖4-2中:
- 第一個層次是UI框架層,其工作為在Surface上繪制UI元素以及響應輸入事件。
- 第二個層次為WMS,其主要工作在于管理Surface的分配、層級順序等。
- 第三層為SurfaceFlinger,負責將多個Surface混合并輸出。
經過這個例子的介紹,相信大家對WMS的功能有了一個初步的了解。接下來,我們要進入WMS的內部,通過其啟動過程一窺它的構成。
- 前言
- 推薦序
- 第1章 開發環境部署
- 1.1獲取Android源代碼
- 1.2Android的編譯
- 1.3在IDE中導入Android源代碼
- 1.3.1將Android源代碼導入Eclipse
- 1.3.2將Android源代碼導入SourceInsight
- 1.4調試Android源代碼
- 1.4.1使用Eclipse調試Android Java源代碼
- 1.4.2使用gdb調試Android C/C 源代碼
- 1.5本章小結
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java層中的Binder分析
- 2.2.1Binder架構總覽
- 2.2.2初始化Java層Binder框架
- 2.2.3窺一斑,可見全豹乎
- 2.2.4理解AIDL
- 2.2.5Java層Binder架構總結
- 2.3心系兩界的MessageQueue
- 2.3.1MessageQueue的創建
- 2.3.2提取消息
- 2.3.3nativePollOnce函數分析
- 2.3.4MessageQueue總結
- 2.4本章小結
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量鍵的處理流程
- 3.2.2通用的音量設置函數setStreamVolume()
- 3.2.3靜音控制
- 3.2.4音量控制小結
- 3.3音頻外設的管理
- 3.3.1 WiredAccessoryObserver 設備狀態的監控
- 3.3.2AudioService的外設狀態管理
- 3.3.3音頻外設管理小結
- 3.4AudioFocus機制的實現
- 3.4.1AudioFocus簡單的例子
- 3.4.2AudioFocus實現原理簡介
- 3.4.3申請AudioFocus
- 3.4.4釋放AudioFocus
- 3.4.5AudioFocus小結
- 3.5AudioService的其他功能
- 3.6本章小結
- 第4章 深入理解WindowManager-Service
- 4.1初識WindowManagerService
- 4.1.1一個從命令行啟動的動畫窗口
- 4.1.2WMS的構成
- 4.1.3初識WMS的小結
- 4.2WMS的窗口管理結構
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的顯示次序
- 4.3.1主序、子序和窗口類型
- 4.3.2通過主序與子序確定窗口的次序
- 4.3.3更新顯示次序到Surface
- 4.3.4關于顯示次序的小結
- 4.4窗口的布局
- 4.4.1從relayoutWindow()開始
- 4.4.2布局操作的外圍代碼分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期處理
- 4.4.5布局DisplayContent
- 4.4.6布局的階段
- 4.5WMS的動畫系統
- 4.5.1Android動畫原理簡介
- 4.5.2WMS的動畫系統框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口動畫
- 4.5.5交替運行的布局系統與動畫系統
- 4.5.6動畫系統總結
- 4.6本章小結
- 第5章 深入理解Android輸入系統
- 5.1初識Android輸入系統
- 5.1.1getevent與sendevent工具
- 5.1.2Android輸入系統簡介
- 5.1.3IMS的構成
- 5.2原始事件的讀取與加工
- 5.2.1基礎知識:INotify與Epoll
- 5.2.2 InputReader的總體流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的讀取與加工總結
- 5.3輸入事件的派發
- 5.3.1通用事件派發流程
- 5.3.2按鍵事件的派發
- 5.3.3DispatcherPolicy與InputFilter
- 5.3.4輸入事件的派發總結
- 5.4輸入事件的發送、接收與反饋
- 5.4.1深入理解InputChannel
- 5.4.2連接InputDispatcher和窗口
- 5.4.3事件的發送
- 5.4.4事件的接收
- 5.4.5事件的反饋與發送循環
- 5.4.6輸入事件的發送、接收與反饋總結
- 5.5關于輸入系統的其他重要話題
- 5.5.1輸入事件ANR的產生
- 5.5.2 焦點窗口的確定
- 5.5.3以軟件方式模擬用戶操作
- 5.6本章小結
- 第6章 深入理解控件系統
- 6.1 初識Android的控件系統
- 6.1.1 另一種創建窗口的方法
- 6.1.2 控件系統的組成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的創建與體系結構
- 6.2.2 通過WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 刪除窗口
- 6.2.5 WindowManager的總結
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的創建及其重要的成員
- 6.3.2 控件系統的心跳:performTraversals()
- 6.3.3 ViewRootImpl總結
- 6.4 深入理解控件樹的繪制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()與臟區域
- 6.4.3 開始繪制
- 6.4.4 軟件繪制的原理
- 6.4.5 硬件加速繪制的原理
- 6.4.6 使用繪圖緩存
- 6.4.7 控件動畫
- 6.4.8 繪制控件樹的總結
- 6.5 深入理解輸入事件的派發
- 6.5.1 觸摸模式
- 6.5.2 控件焦點
- 6.5.3 輸入事件派發的綜述
- 6.5.4 按鍵事件的派發
- 6.5.5 觸摸事件的派發
- 6.5.6 輸入事件派發的總結
- 6.6 Activity與控件系統
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的創建與顯示
- 6.7 本章小結
- 第7章 深入理解SystemUI
- 7.1 初識SystemUI
- 7.1.1 SystemUIService的啟動
- 7.1.2 狀態欄與導航欄的創建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的體系結構
- 7.2 深入理解狀態欄
- 7.2.1 狀態欄窗口的創建與控件樹結構
- 7.2.2 通知信息的管理與顯示
- 7.2.3 系統狀態圖標區的管理與顯示
- 7.2.4 狀態欄總結
- 7.3 深入理解導航欄
- 7.3.1 導航欄的創建
- 7.3.2 虛擬按鍵的工作原理
- 7.3.3 SearchPanel
- 7.3.4 關于導航欄的其他話題
- 7.3.5 導航欄總結
- 7.4 禁用狀態欄與導航欄的功能
- 7.4.1 如何禁用狀態欄與導航欄的功能
- 7.4.2 StatusBarManagerService對禁用標記的維護
- 7.4.3 狀態欄與導航欄對禁用標記的響應
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系統中的漫游過程
- 7.5.2 SystemUIVisibility發揮作用
- 7.5.3 SystemUIVisibility總結
- 7.6 本章小結
- 第8章 深入理解Android壁紙
- 8.1 初識Android壁紙
- 8.2深入理解動態壁紙
- 8.2.1啟動動態壁紙的方法
- 8.2.2壁紙服務的啟動原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁紙的銷毀
- 8.2.5 理解Engine的回調
- 8.3 深入理解靜態壁紙-ImageWallpaper
- 8.3.1 獲取用作靜態壁紙的位圖
- 8.3.2 靜態壁紙位圖的設置
- 8.3.3 連接靜態壁紙的設置與獲取-WallpaperObserver
- 8.4 WMS對壁紙窗口的特殊處理
- 8.4.1 壁紙窗口Z序的確定
- 8.4.2 壁紙窗口的可見性
- 8.4.3 壁紙窗口的動畫
- 8.4.4 壁紙窗口總結
- 8.5 本章小結