一、WIndow和windowManager
Window是一個抽象類,它的具體實現是PhoneWindow,創建一個window很簡單,只需要創建一個windowManager即可,window具體實現在windowManagerService中,windowManager和windowManagerService的交互是一個IPC的過程。
下面是用windowManager的例子:
flags和type兩個屬性很重要,下面對一些屬性進行介紹,首先是flags:
FLAG_NOT_TOUCH_MODAL表示不需要獲取焦點,也不需要接收各種輸入,最終事件直接傳遞給下層具有焦點的window。
FLAG_NOT_FOCUSABLE:在此window外的區域單擊事件傳遞到底層window中。當前的區域則自己處理,這個一般都要設置,很重要。
FLAG_SHOW_WHEN_LOCKED :開啓可以讓window顯示在鎖屏界面上。
再來看下type這個參數:
window有三種類型:應用window,子window,系統window。應用類對應一個Activity,子Window不能單獨存在,需要附屬在父Window上,比如常用的Dialog。系統Window是需要聲明權限再創建的window,如toast等。
window有z-ordered屬性,層級越大,越在頂層。應用window層級1-99,子window1000-1999,系統2000-2999。這此層級對應着windowManager的type參數。系統層級常用的有兩個TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR。比如想用TYPE_SYSTEM_ERROR,只需
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR。還要添加權限<uses-permission andorid:name="android.permission.SYSTEM_ALERT_WINDOW"/>。
有了對window的基本認識之後,我們來看下它底層如何實現加載View的。
二、window的創建
其實Window的創建跟之前我寫的一篇博客LayoutInflater源碼分析有點相似。Window的創建是在Activity創建的attach方法中,通過PolicyManager的makeNewWindow方法。Activity中實現了Window的Callback接口,因此當window狀態改變時就會回調Activity方法。如onAttachedToWindow等。PolicyManager的真正實現類是Policy,看下它的代碼:
到此Window創建完成。
下面分析view是如何附屬到window上的。看Activity的setContentView方法。
兩部分,設置內容和設置ActionBar。window的具體實現是PhoneWindow,看它的setContent。
看到了吧,又是分析它。
這裏分三步執行:
1.如果沒有DecorView,在installDecor中的generateDecor()創建DecorView。之前就分析過,這次就不再分析它了。
2.將View添加到decorview中的mContentParent中。
3.回調Activity的onContentChanged接口。
經過以上操作,DecorView創建了,但還沒有正式添加到Window中。在ActivityResumeActivity中首先會調用Activity的onResume,再調用Activity的makeVisible,makeVisible中真正添加view ,代碼如下:
通過上面的addView方法將View添加到Window。
三、Window操作View內部機制
1.window的添加
一個window對應一個view和一個viewRootImpl,window和view通過ViewRootImpl來建立聯繫,它並不存在,實體是view。只能通過 windowManager來操作它。
windowManager的實現類是windowManagerImpl。它並沒有直接實現三大操作,而是委託給WindowManagerGlobal。addView的實現分爲以下幾步:
1).檢查參數是否合法。
2).創建ViewRootImpl並將View添加到列表中。
3).通過ViewRootImpl來更新界面並完成window的添加過程 。
root.setView(view, wparams, panelParentView);
上面的root就是ViewRootImpl,setView中通過requestLayout()來完成異步刷新,看下requestLayout:
接下來通過WindowSession來完成window添加過程,WindowSession是一個Binder對象,真正的實現類是 Session,window的添加是一次IPC調用。
在Session內部會通過WindowManagerService來實現Window的添加。
在WindowManagerService內部會爲每一個應用保留一個單獨的session。
2.window的刪除
看下WindowManagerGlobal的removeView:
首先調用findViewLocked來查找刪除view的索引,這個過程就是建立數組遍歷。然後再調用removeViewLocked來做進一步的刪除。
真正刪除操作是viewRootImpl來完成的。windowManager提供了兩種刪除接口,removeViewImmediate,removeView。它們分別表示異步刪除和同步刪除。具體的刪除操作由ViewRootImpl的die來完成。
由上可知如果是removeViewImmediate,立即調用doDie,如果是removeView,用handler發送消息,ViewRootImpl中的Handler會處理消息並調用doDie。重點看下doDie:
主要做四件事:
1.垃圾回收相關工作,比如清數據,回調等。
2.通過Session的remove方法刪除Window,最終調用WindowManagerService的removeWindow
3.調用dispathDetachedFromWindow,在內部會調用onDetachedFromWindow()和onDetachedFromWindowInternal()。當view移除時會調用onDetachedFromWindow,它用於作一些資源回收。
4.通過doRemoveView刷新數據,刪除相關數據,如在mRoot,mDyingViews中刪除對象等。
3.更新window
看下WindowManagerGlobal中的updateViewLayout。
通過viewRootImpl的setLayoutParams更新viewRootImpl的layoutParams,接着scheduleTraversals對view重新佈局,包括測量,佈局,重繪,此外它還會通過WindowSession來更新window。這個過程由WindowManagerService實現。這跟上面類似,就不再重複,到此Window底層源碼就分析完啦。
本文是我從以下地址拷貝來的: http://lpqsun-126-com.iteye.com/blog/1409989
保存下來,仔細閱讀。
我想大多數人,對於這3個東西的概念能區分,但是具體區別在哪卻很難說出來。
我這裏根據我個人的理解來講講我個人對這3個概念的理解。當然這裏設計到通用的事件窗口模型等通用GUI設計,我這裏就不打算講了,純粹從概念上來進行區分。
Activity是Android應用程序的載體,允許用戶在其上創建一個用戶界面,並提供用戶處理事件的API,如onKeyEvent, onTouchEvent等。 並維護應用程序的生命週期(由於android應用程序的運行環境和其他操作系統不同,android的應用程序是運行在框架之內,所以他的應用程序不能噹噹從進程的級別去考慮,而更多是從概念上去考慮。android應用程序是由多個活動堆積而成,而各個活動又有其獨立的生命週期)。Activity本身是個龐大的載體,可以理解成是應用程序的載體,如果木有Activity,android應用將無法運行。也可以理解成android應用程序的入口。Acivity的實例對象由系統維護。系統服務ActivityManager負責維護Activity的實例對象,並根據運行狀態維護其狀態信息。
但在用戶級別,程序員可能根願意理解成爲一個界面的載體。但僅僅是個載體,它本身並不負責任何繪製。Activity的內部實現,實際上是聚了一個Window對象。Window是一個抽象類,它的具體是在android_src_home/framework/policies/base/phone/com/android/internal/policy/impl目錄下的PhoneWindow.java。
當我們調用Acitivity的 setContentView方法的時候實際上是調用的Window對象的setContentView方法,所以我們可以看出Activity中關於界面的繪製實際上全是交給Window對象來做的。繪製類圖的話,可以看出Activity聚合了一個Window對象。
下面是PhoneWindow中的setContentView方法的實現:
- @Override
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- if (mContentParent == null) {
- installDecor();
- } else {
- mContentParent.removeAllViews();
- }
- mContentParent.addView(view, params);
- final Callback cb = getCallback();
- if (cb != null) {
- cb.onContentChanged();
- }
- }
Window內部首先判斷mContentParent是否爲空,然後調用installDecor方法(安裝裝飾器),我們看看這個方法如何實現的
- private void installDecor() {
- if (mDecor == null) {
- mDecor = generateDecor();
- mDecor.setIsRootNamespace(true);
- }
- if (mContentParent == null) {
- mContentParent = generateLayout(mDecor);
- mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
- if (mTitleView != null) {
- if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
- View titleContainer = findViewById(com.android.internal.R.id.title_container);
- if (titleContainer != null) {
- titleContainer.setVisibility(View.GONE);
- } else {
- mTitleView.setVisibility(View.GONE);
- }
- if (mContentParent instanceof FrameLayout) {
- ((FrameLayout)mContentParent).setForeground(null);
- }
- } else {
- mTitleView.setText(mTitle);
- }
- }
- }
- }
在該方法中,首先創建一個DecorView,DecorView是一個擴張FrameLayout的類,是所有窗口的根View。我們在Activity中調用的setConctentView就是放到DecorView中了。這是我們類圖的聚合關係如下:
Activity--->Window--->DecorView
這是我們得出這3個類之間最直接的一個關係。
我們詳細分析一下,類對象是如何被創建的。
先不考慮Activity的創建(因爲 Acitivity的實例由ActivityManager維護,是在另一個進程設計到IPC的通信,後面會講到),而考慮Window和View的創建。
Activity被創建後,系統會調用它的attach方法來將Activity添加到ActivityThread當中。我們找到Activity的attach方法如下:
- final void attach(Context context, ActivityThread aThread,
- Instrumentation instr, IBinder token, int ident,
- Application application, Intent intent, ActivityInfo info,
- CharSequence title, Activity parent, String id,
- Object lastNonConfigurationInstance,
- HashMap<String,Object> lastNonConfigurationChildInstances,
- Configuration config) {
- attachBaseContext(context);
- mWindow= PolicyManager.makeNewWindow(this);
- mWindow.setCallback(this);
- if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
- mWindow.setSoftInputMode(info.softInputMode);
- }
- mUiThread = Thread.currentThread();
- mMainThread = aThread;
- mInstrumentation = instr;
- mToken = token;
- mIdent = ident;
- mApplication = application;
- mIntent = intent;
- mComponent = intent.getComponent();
- mActivityInfo = info;
- mTitle = title;
- mParent = parent;
- mEmbeddedID = id;
- mLastNonConfigurationInstance = lastNonConfigurationInstance;
- mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;
- mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
- if (mParent != null) {
- mWindow.setContainer(mParent.getWindow());
- }
- mWindowManager = mWindow.getWindowManager();
- mCurrentConfig = config;
- }
我們看紅色的代碼部分,就是創建Window對象的代碼。感興趣的同學可以跟蹤去看看具體是如何創建的。其實很簡單,其內部實現調用了Policy對象的makeNewWindow方法,其方法直接new了一個PhoneWindow對象如下:
public PhoneWindow makeNewWindow(Context context) {
returnnew PhoneWindow(context);
}
這時我們已經可以把流程串起來,Activity創建後系統會調用其attach方法,將其添加到ActivityThread當中,在attach方法中創建了一個window對象。
下面分析View的創建。我們知道Window聚合了DocerView,當用戶調用setContentView的時候會把一顆View樹仍給DocerView.View樹是已經創建好的實例對象了,所以我們研究的是DocerView是個什麼東西,它是如何被創建的。
我們回頭看看Window實現裏邊的setContentView方法,我們看上面代碼的紅色部分setContentView-> installDecor-> generateDecor.
generateDecor直接new了一個DecorView對象:
protected DecorView generateDecor() {
returnnew DecorView(getContext(), -1);
}
我們可以去看看DecorView的實現,它是PhoneWindow的一個內部類。實現很簡單,它默認會包含一個灰色的標題欄,然後在標題欄下邊會包含一個空白區域用來當用戶調用setContentView的時候放置用戶View,並傳遞事件,這裏不做詳細分析,感興趣同學可以自己研究研究。
當DecorView創建好之後再回到Window中的setContentView方法中來,見上面代碼藍色部分,調用
mContentParent.addView(view, params);來將用戶的View樹添加到DecorView中。
到這時爲止,我想我們已經很清晰的認識到它們3者之間的關係,並知道其創建流程。
現在總結一下:
Activity在onCreate之前調用attach方法,在attach方法中會創建window對象。window對象創建時並木有創建Decor對象對象。用戶在Activity中調用setContentView,然後調用window的setContentView,這時會檢查DecorView是否存在,如果不存在則創建DecorView對象,然後把用戶自己的View 添加到DecorView中。
上篇講解了3個對象之間的關係和創建的時機。這篇講解窗口是如何被繪製出來的。
首先,我們看一個概念。就是View的draw方法的doc:
Manually render this view (and all of its children) to the given Canvas.
意思是說把View繪製在畫布上。個人覺得這個概念很重要,View和Canvas 的關係,按常規的思維,肯定認爲View聚合了Canvas對象,然後在View的onDraw 方法中,在View中繪製圖形。實際上恰恰相反,Canvas 是由系統提供,view通過draw方法來把自身繪製在畫布上。如果這樣來理解的話,很多東西理解起來就很自然了。系統在window中提供一個Canvas對象,DocerView通過調用draw方法來將自己繪製到canvas上。draw方法實際上是一個遞歸方法,他會循環調用孩子View的draw方法來完成整棵樹的繪製。所以實際上一個界面的繪製所用的Cavans是同一個對象。Canvas內部聚合了Matrix對象來實現座標系的變換。
這裏將的是題外話,只是想讓大家理解一個東西。
下面回到系統如何來繪製一個窗口。
android 系統提供了WindowManager,WindowManager顧名思義窗口管理器。實際上它只是對WindowManager服務做了一個包裝。其內部實現通過ISessionWindow和IWindow接口來和WindowManager服務來通信,這裏設計到IPC的概念。IPC即進程間的通訊,ANDROID通過IBinder接口來實現,IBinder通過transact方法來實現進程間的交互,這是一個使用很不友好的接口,好在android還提供了aidl的更人性化的接口,它使得IPC通信就像調用普通的JAVA方法那樣便捷。不瞭解aidl的同學可以自行研究研究(其實我自己也是半桶水,瞭解概念,而用的不熟悉)
我來看Window是如何被添加到WindowManager的.
Activity有一個public的方法setVisible用來控制Activity的窗口是否顯示。
我們看其內部實現發現其調用了makeVisible方法,該方法就是讓Window顯示在屏幕當中的方法,實現如下:
- void makeVisible() {
- if (!mWindowAdded) {
- ViewManager wm = getWindowManager();
- wm.addView(mDecor, getWindow().getAttributes());
- mWindowAdded = true;
- }
- mDecor.setVisibility(View.VISIBLE);
- }
這時我們立馬就明白了,Activity通過Context來獲取WindowManager對象,然後把Window對象的DocerView添加到了WindowManager 服務中,所以android的窗口的創建和顯示並不是在同一個進程中,而是把窗口的繪製和管理交給了專門的WindowManager服務,這也是android framework給我提供的基礎服務。
在上面綠色的代碼當中,我們看看mDeocr是在哪被創建的。
- public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
- // Update window manager if: we have a view, that view is
- // attached to its parent (which will be a RootView), and
- // this activity is not embedded.
- if (mParent == null) {
- View decor = mDecor;
- if (decor != null && decor.getParent() != null) {
- getWindowManager().updateViewLayout(decor, params);
- }
- }
- }
搜索代碼發現其創建的地方如上面紅色代碼。
這時我們已經知道,Activity創建好Window之後只要調用WindowManager 的addView方法來將Window的DocerView添加進去即可是使Window顯示出來。還方法Window其實是Activity中的概念,在WindowManager中是直接和View打交道的。
下面我們開始研究WindowManager對象,打開其源代碼,發現它是一個接口,且只是簡單的擴展了ViewManager接口.並增加了一個方法
getDefaultDisplay():Display. 內部還有一個繼承自ViewGroup.LayoutParam的內部類。
我們在framework/base/core/java/android/view/WindowManagerImpl.java 中找到其實現類。
我們找到其核心的實現方法addView 方法,如下:
- private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
- {
- if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);
- if (!(params instanceof WindowManager.LayoutParams)) {
- throw new IllegalArgumentException(
- "Params must be WindowManager.LayoutParams");
- }
- final WindowManager.LayoutParams wparams
- = (WindowManager.LayoutParams)params;
- ViewRoot root;
- View panelParentView = null;
- synchronized (this) {
- // Here's an odd/questionable case: if someone tries to add a
- // view multiple times, then we simply bump up a nesting count
- // and they need to remove the view the corresponding number of
- // times to have it actually removed from the window manager.
- // This is useful specifically for the notification manager,
- // which can continually add/remove the same view as a
- // notification gets updated.
- int index = findViewLocked(view, false);
- if (index >= 0) {
- if (!nest) {
- throw new IllegalStateException("View " + view
- + " has already been added to the window manager.");
- }
- root = mRoots[index];
- root.mAddNesting++;
- // Update layout parameters.
- view.setLayoutParams(wparams);
- root.setLayoutParams(wparams, true);
- return;
- }
- // If this is a panel window, then find the window it is being
- // attached to for future reference.
- if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
- wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
- final int count = mViews != null ? mViews.length : 0;
- for (int i=0; i<count; i++) {
- if (mRoots[i].mWindow.asBinder() == wparams.token) {
- panelParentView = mViews[i];
- }
- }
- }
- root =newViewRoot(view.getContext());
- root.mAddNesting = 1;
- view.setLayoutParams(wparams);
- if (mViews == null) {
- index = 1;
- mViews = new View[1];
- mRoots = new ViewRoot[1];
- mParams = new WindowManager.LayoutParams[1];
- } else {
- index = mViews.length + 1;
- Object[] old = mViews;
- mViews = new View[index];
- System.arraycopy(old, 0, mViews, 0, index-1);
- old = mRoots;
- mRoots = new ViewRoot[index];
- System.arraycopy(old, 0, mRoots, 0, index-1);
- old = mParams;
- mParams = new WindowManager.LayoutParams[index];
- System.arraycopy(old, 0, mParams, 0, index-1);
- }
- index--;
- mViews[index] = view;
- mRoots[index] = root;
- mParams[index] = wparams;
- }
- // do this last because it fires off messages to start doing things
- root.setView(view, wparams, panelParentView);
- }
我們看看我標記未紅色的兩行代碼 root =newViewRoot(view.getContext());和 root.setView(view, wparams, panelParentView);
創建了一個ViewRoot對象,然後把view添加到ViewRoot中。
ViewRoot對象是handler的一個實現,其聚合了ISessionWindow和IWindow對象來和WindowManager服務進行IPC通信。