android中window和windowManager原始碼分析(android-api-23)
一、前言
在android中window無處不在,如activity、dialog、toast等。它是view所依附的載體,每個window都對應於一個View和一個ViewRootImpl。ViewRootImpl就是Window和view的連線紐帶。windowManager顧名思義是實現對window的管理,事實上,是實現對依附於window中的view的管理。
二、從dialog開始
dialog是大家都很熟悉的東西,在實際的專案開發中幾乎都能用到。因此很有必要了解下dialog的內部實現,以明白其實現原理。對於dialog的使用最簡單的形式,就是下列這種形式:
只需要上面簡單的一句程式碼,即可出show出來一個對話方塊來,根據前言可知,這裡顯然涉及到了window的概念。因此,這裡先分兩個方向走,一個是dialog的構造方法,一個是show方法,來探究window的概念。new Dialog(MainActivity.this).show();
2.1 dialog的構造方法
dialog有多個構造方法,但最終都會呼叫下面的構造方法,如下所示。
上面的程式碼比較簡單,核心是產生了一個WindowManager和Window物件,並設定了一些回撥方法,以便進行window事件的相關處理。我們都知道dialog是依附於activity而存在的,但能獨立處理一些事件,上面的程式碼給出了答案。其實從dialog的構造方法來看顯然沒有做什麼有效的工作,so,真正的主題應該是在show方法中了。Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == 0) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } //這裡出現了我們將要探討的windowManager,事實上無論是activity還是dialog都會持有windManager服務 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); //這裡第一次出現了我們要探討了window,即產生了一個實際的PhoneWindow物件,事實上,Window僅僅是個抽象類, //對於手機,一般是由PhoneWindow進行實現 final Window w = new PhoneWindow(mContext); mWindow = w; //從下面可以看出來,dialog有自己處理事件的能力,因為window在此設定了callback w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); }
2.2 dialog的show方法分析
上面程式碼清晰了展現出了dialog show的過程。主要關注幾個點:dispathOnCreate、mWindow.getDecorView()以及public void show() { //這裡處理了已經show了的情況,這裡無需討論 if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; //這裡如果沒有create則會呼叫dispatchOncreate,通過該方法,最終會呼叫實際的dialog中的onCreate方法 //之所以說是實際的,因為我們可能會繼承dialog進而複寫onCreate方法 if (!mCreated) { dispatchOnCreate(null); } //dialog所有的初始化工作建議在onstart去做 onStart(); //這裡,引入了decorView,這個是所有檢視的底層佈局,型別為FrameLayout mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } try { //windowManager將decorView加入了window視窗,這裡真正涉及到windowManager對window的管理了(實際上是對view進行管理) mWindowManager.addView(mDecor, l); mShowing = true; //這裡真正的去show了,不在討論的範圍了。 sendShowMessage(); } finally { } }
mWindowManager.addView(mDecor, l);在本章節我們只簡單分析dispathOnCreate。這樣也會對mDecor的由來有個認知。至於mWindowManager.addView(mDecor, l)分析將會真正引出本文的主題。
2.3 dispatOnCreate
在上一小節中,dialog show的過程中,呼叫了dispatOnCreate方法,改方法如下:
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
即呼叫了onCreate方法,檢視dialog的onCreate方法可知,該方法是個空方法,但是建議在該方法中初始化檢視相關如,呼叫setContentView:/**
* Similar to {@link Activity#onCreate}, you should initialize your dialog
* in this method, including calling {@link #setContentView}.
* @param savedInstanceState If this dialog is being reinitalized after a
* the hosting activity was previously shut down, holds the result from
* the most recent call to {@link #onSaveInstanceState}, or null if this
* is the first time.
*/
protected void onCreate(Bundle savedInstanceState) {
}
因此,這裡可以參考子類的實現,子類的實現方式可參考下面程式碼:@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設定檢視
setContentView(mView);
//初始化dialog的一些資料
initDialog();
}
因此這裡主要分析setContentView,這個實際上是dialog內部方法,實現為:/**
* Set the screen content to an explicit view. This view is placed
* directly into the screen's view hierarchy. It can itself be a complex
* view hierarchy.
*
* @param view The desired content to display.
*/
public void setContentView(View view) {
//這裡顯然呼叫了mWindow進行檢視設定,這裡的mWindow實際上就是構造方法中的PhoneWindow物件。
//最終將contentView依附於window之上,但這個是使用者自定義內容的檢視。
mWindow.setContentView(view);
}
//mWindow實際上PhoneWindow物件。因此setContentView的實現就是PhoneWindow中的setContentView實現
//setContentView最終會呼叫下面的程式碼
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//這裡就是生成decorView,dialog show方法中的mWindow.getDecor方法獲取的實際上就是在此時生成的。
//decorview實際上是android中最頂層的一個佈局,其繼承於FrameLayout,是所有view的viewGroup,所有的view都將包括於該ViewGroup中,
//包括剛剛我們setContentView中的展示的自定義view
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
//從這裡可以看出,我們剛剛設定的contentView會被設定到content區域,這個mContentParent實際上是通過下面程式碼獲取的
//ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);從程式碼可以看出,這裡獲取了佈局中的
//內容區域
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
至此,我們瞭解到了整個view的生成過程,接下來就是真正引入主題,即將view新增至window中,並交給windowManager進行管理。三、我們的主角window和windowManager
3.1 從addView說起
在對dialog實現原始碼進行分析的時候,在show方法的最後重要的一步就是執行了mWindowManager.addView(mDecor, l);方法,so,就從這裡開始。經過對addView的追溯發現,其實際上是ViewManager中定義的方法,定義如下所示:
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
*
Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *
Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
顧名思義,從上述程式碼中可以看出,該介面實際上提供了對view的增、刪、改的工作,那麼這為什麼和window的管理連線到一起呢?實際上WindowManager繼承了該介面,檢視WindowManager的實現程式碼可知:public interface WindowManagerextendsViewManager
而Dialog在構造方法中獲取的mWindow型別正是WindowManager,所以自然可以擁有對view的增、刪、改的功能,同時也說明了,windowManager本質上是對View進行管理。但是WindowManager顯然依然是個介面,其具體實現在哪兒?其實是在WindowManagerImpl中,進入其addView方法就可以明白相關實現。WindowManagerImpl中的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//顯然,這裡全部委託給了mGlobal來進行實現
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
該方法的實現很簡單,重要的mGlobal的實現,跟蹤發現mGlobal是一個WindowManagerGlobal型別的物件,其addView的實現方法為:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//這裡省略一些檢查,如view是否為null等....
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//1、這裡有個parentWindow判斷,為什麼?
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// 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.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//2、這裡產生了ViewRootImpl型別的root物件,我們終於見到前言中提到的這個角色了
//這個物件很重要,前言中說這個是view和window中的連線紐帶,那麼ViewRootImpl具體是幹什麼的?
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//3、這裡出現了多個add呼叫,那麼這些層意又是什麼?
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//顯然,這裡將傳入的view通過root的setView完成了設定,上面提到root的型別是ViewRootImpl,
//因此很有必要探究下ViewRootImpl的setView實現。在這裡真正完成了檢視的展現。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
重要的程式碼註釋我都已經標明,首先分析下注釋中的幾個問題:(1)window實際上有三種不同的型別,分別是系統window、應用window以及子window,而且不同型別的window有層級的概念,類似於html中的z-index,因此我們能看到系統視窗可以覆蓋到應用window之上等。Toast就是系統window,activity(activity是基視窗,其它所有的應用視窗都依賴於改視窗)、dialog就是普通的應用window(對於dialog而言子類不那麼絕對,因為很多子類都可以修改其window型別),而popupWindow則是子window。
(2)ViewRootImpl這個是Window和view之間的紐帶,這裡暫不分析,下節分析
(3)對於這些容器可參見WindowManagerGlobal中的定義
//儲存的是所有Window對應的View
private final ArrayList mViews = new ArrayList();
//儲存的是所有Window所對應的ViewRootImpl
private final ArrayList mRoots = new ArrayList();
//儲存所有window對應的佈局引數
private final ArrayList mParams =
new ArrayList();
//儲存所有被removeView方法中刪除的view
private final ArraySet mDyingViews = new ArraySet();
ViewRootImpl中的setView實現ViewRootImpl的setView方法,比較長,這裡貼上一些必要的程式碼:public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//省略部分程式碼....
requestLayout();
//省略部分程式碼....
//這裡呼叫了mWindowSession的addToDisplay方法,完成真正的window新增
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
//省略部分程式碼....
}}}
這裡但最後會呼叫一個方法:requestLayout();該方法真正完成檢視的非同步重新整理請求,起程式碼如下:@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
由此可知具體實現在scheduleTraversals,這裡就是view的繪製入口,此處不再深入,主要看window的新增。從上面程式碼中可以看出window的新增是有mWindowSession來完成的,mWindowSession型別是IWindowSession,它是個Binder物件,真正的實現類是Session,Binder大家應該熟悉,是android提供的一種ipc通訊機制,因此,window的新增過程實際上是一次ipc的呼叫。Sessin中的addToDisplay程式碼如下:public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
//這裡呼叫了mService的addWindow方法,mService的型別是WindowManagerService,因此具體實現就再這個類中
//這裡會根據傳入的引數生成windstate,並對一些token進行處理,至此整個window完成了新增
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
至此,我們以Dialog為引子,完成的看到了整個window的新增流程。從這個流程可以看出,dialog(當然也包括其它檢視)中的檢視首先會以DecorView為根佈局而存在,而decorView
依附於window上。在android中window的增、刪、改,實際上都是基於view進行操作的,這也說明window是個相對虛擬的概念。window的新增完成了,或許會有很多人想知道window是怎麼樣被remove的呢?3.2 從removeViewImmediate結束
或許讀者會有疑問,為什麼從removeViewImmediate結束?且等我慢慢道來。我們依然從dialog入手。在上章節中,我們知道dialog的show過程實際上就是window的新增過程,從dialog的show開始,到dialog消失之前顯然沒有任何機會看到window的remove過程,因為window顯然一直存在。所以,所有的神祕應該隱藏dialog的dismiss方法中。dialog的dismiss方法中基本沒有做什麼事情,就是呼叫了dismissDialog,故直接看dismissDialog的程式碼:
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
//重點在這裡,可以看到整個dismiss的過程就這裡在remove了。
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
程式碼中的mWindowManager我們已經很熟悉了,就是針對window進行管理的一個介面,其具體實現在WindowManagerImpl中:public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
不出所料,這裡依然交給了mGlobal進行view管理,mGlobal是WindowManagerGlobal物件,其removeView實現如下:public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
//待remove view的索引
int index = findViewLocked(view, true);
//mRoots前文中介紹過,儲存著每一個viewRootImpl物件
View curView = mRoots.get(index).getView();
//重點是這裡,真正對view進行了remove操作
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
removeViewLocked的實現方法如下:private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
//重點在ViewRootImpl中的die方法中
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
//前文提到過,這個暫存remove過的view,就在這裡,因為remove需要一個過程
//那麼這裡最後在哪兒刪除?下文將有交代
mDyingViews.add(view);
}
}
}
ViewRootImpl中的die方法實現如下:
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
為便於分析這裡假設立即刪除(即同步刪除,實際上多數情況下都是非同步刪除,即發個MGS_DIE的訊息即可返回了),直接看ViewRootImpl中的doDie()方法:void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
//這裡是移除工作的重點,下文將進行分析
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
//硬體渲染destroy
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
//這個大家應該都清楚,檢視渲染時需要的,這裡釋放記憶體佔用記憶體
mSurface.release();
}
}
mAdded = false;
}
//這裡將解釋上文中view到底什麼時候被徹底remove
WindowManagerGlobal.getInstance().doRemoveView(this);
}
在解析dispatchDetachedFromWindow之前,先看下doRemoveView方法,該方法在WindowManagerGlobal中,具體實現如下:void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
//以下語句,正式完成了待刪除的view的remove工作
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {
doTrimForeground();
}
}
最後看remove過程的重點,ViewRootImpl中的dispatchDetachedFromWindow實現如下:void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
//將檢視從window中移除,此方法會回撥onDetachedFromWindow方法,從該方法的說明來看,
//這個時候已經沒有用於繪製的surface了,因此這裡可以做些資源回收工作
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
//這裡是重點,很顯然這裡真正的完成了我們window的remove工作,下面會分析。
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
程式碼中最重要的部分便是mWindowSession.remove方法,mWindowSession在前文已經提到過。在view的新增addView的過程中,最終還是通過mWindowSession的addToDisplay方法來完成的,在這個方法中,addToDisplay呼叫了WindowManagerService中的addWindow方法完成了window的新增工作,這個過程是一個ipc的過程。同樣的,mWindowSession.remove方法最後依然會呼叫WindowManagerService中的removeWindow方法,實現如下:public void removeWindow(Session session, IWindow client) {
synchronized(mWindowMap) {
//這裡和addWindow時相對應,windowstate正是在那個時候生成的。
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return;
}
//根據windowstate對window進行真正的移除操作
removeWindowLocked(win);
}
}
至此,window的remove正式完成。四、總結
本文以dialog的展示和消失為切入點,分析了android中window和windowManager的實現機制,這個過程的細節一定是比較複雜的,但是我們只關心這條主線,從而對window的新增和刪除過程有個清晰的過程。基於dialog的分析實現,同樣適用於activity、toast中window的實現,雖有不同,但大同小異。
最後,本文的文字純手動打字,如若出現錯別字還請諒解。