一、前言
在android中window无处不在,如activity、dialog、toast等。它是view所依附的载体,每个window都对应于一个View和一个ViewRootImpl。ViewRootImpl就是Window和view的连接纽带。windowManager顾名思义是实现对window的管理,事实上,是实现对依附于window中的view的管理。
二、从dialog开始
dialog是大家都很熟悉的东西,在实际的项目开发中几乎都能用到。因此很有必要了解下dialog的内部实现,以明白其实现原理。对于dialog的使用最简单的形式,就是下列这种形式:
new Dialog(MainActivity.this).show();
只需要上面简单的一句代码,即可出show出来一个对话框来,根据前言可知,这里显然涉及到了window的概念。因此,这里先分两个方向走,一个是dialog的构造方法,一个是show方法,来探究window的概念。
2.1 dialog的构造方法
dialog有多个构造方法,但最终都会调用下面的构造方法,如下所示。
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);
}
上面的代码比较简单,核心是产生了一个WindowManager和Window对象,并设置了一些回调方法,以便进行window事件的相关处理。我们都知道
dialog是依附于activity而存在的,但能独立处理一些事件,上面的代码给出了答案。
其实从dialog的构造方法来看显然没有做什么有效的工作,so,真正的主题应该是在show方法中了。
2.2 dialog的show方法分析
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 {
}
}
上面代码清晰了展现出了dialog show的过程。主要关注几个点:dispathOnCreate、mWindow.getDecorView()以及
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进行管理。
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的实现代码可知:@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;
}
}
重要的代码注释我都已经标明,首先分析下注释中的几个问题:
//存储的是所有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的呢?
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);
}
}
}
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正式完成。
最后,本文的文字纯手动打字,如若出现错别字还请谅解。