本文主题
关于WindowManager这个复杂的系统,本文会基于Android9.0源码,把其中的关键代码截取出来进行分析,并通过问答的形式来进行叙述,最终回答以下几个问题:
- WindowManager是什么?它的作用是什么?
- Window和WindowManager如何关联?
- Window、WindowManager和WindowManagerService三者有什么关系?
- Window有哪些类型?
- Window在Activity启动过程中的作用?
- Window如何处理View的添加、移除和更新?
WindowManager是什么
The interface that apps use to talk to the window manager.
Each window manager instance is bound to a particular Display. To obtain a WindowManager for a different display, use Context#createDisplayContext to obtain a Context for that display, then use Context.getSystemService(Context.WINDOW_SERVICE) to get the WindowManager.
The simplest way to show a window on another display is to create a Presentation. The presentation will automatically obtain a WindowManager and Context for that display.
用通俗一点的话来讲就是:
WindowManager是一个实现了ViewManager的接口,至于它是干嘛用的,官方文档并没有详细说明,从名字上我们可以知道它和显示有关,用于管理Window,具体作用还是直接看源码吧。
如何获取WindowManager
正如官方文档所说,我们可以直接通过 Context.getSystemService(Context.WINDOW_SERVICE) 来获取WindowManager
继承ViewManager接口
WindowManager继承ViewManager接口,而ViewManager接口很简单,只有三个方法
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>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.
*/
// 添加View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 移除View
public void removeView(View view);
}
定义了许多Flags
在WindowManager.LayoutParams里面有许多Flags,这些Flag的作用就是在创建Window的时候用于区分这个Window到底是什么类型的,关于Window类型的问题我们会在后面再详细说明。
下面列举了一小部分Flags
public static final int TYPE_BASE_APPLICATION = 1;
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
public static final int LAST_APPLICATION_WINDOW = 99;
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
// 后面省略N个Flag
通过int类型的Type,我们可以区分不同的Window类型。
大家也可以直接跳到第四个问题查看:Window有哪些类型?
回答:WindowManager是什么
WindowManager负责的事情其实并不多,主要完成一些配置工作(定义Window类型的Flags,以及LayoutParams静态内部类),具体的跨进程通信还是要看WindowManagerService,而对View的操作则是通过WindowManagerGlobal来进行。
Window和WindowManager如何关联
说了这么多,分析了一通WindowManager这个类的源码,我们只知道它是一个接口,但是还不知道它具体是怎么使用的。别急,接下来就轮到Window登场了。
关联的关键:Window.java
我们在Window.java找到以下代码:
/**
* Set the window manager for use by this Window to, for example,
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
}
/**
* Set the window manager for use by this Window to, for example,
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 通过WindowManagerImpl创建WindowManager对象,并赋值给mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
这两个setWindowManager()方法分别在Activity和Dialog中被调用了。说明在Activity创建,Toast显示的过程中都需要用到WindowManager。
注意:mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
这一行代码就是Window和WindowManager产生化学反应的关键!
首先需要看看WindowManagerImpl是什么
WM的实现类:WindowManagerImpl.java
WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
其实Window.java里面调用的这个方法,就是创建一个Impl对象。
在WindowManagerImpl中,我们会看到有个成员变量
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
这个WindowManagerGlobal 正是用于对View进行操作的实际对象。
无论是addView(), updateView(), removeView(),都是通过这个对象进行操作的。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 调用WindowManagerGlobal 的addView()方法
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 同理
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
// 同理
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
// 同理
mGlobal.removeView(view, true);
}
关于WindowManagerGlobal 怎么处理View的逻辑,我们在后面会继续解析。这里还是先回到Window和WindowManager这两者是如何关联这个问题上来。
如果大家有细心留意的话就能看到,WindowManagerImpl的构造方法里面有一个parentWindow 的参数,这个参数的类型是Window,也就是说,当我们在Activity或者Dialog调用setWindowManager()的时候,就会把当前Window作为参数传递过来,在创建WindowManager的同时把这两者给关联起来。
回答:Window和WindowManager如何关联
在创建Activity或者Dialog的时候会调用Window.setWindowManager()方法,然后把当前Window作为参数传递到WindowManagerImpl的createLocalWindowManager()方法中,在创建WindowManager对象的时候把Window关联起来。
Window、WindowManager和WindowManagerService三者有什么关系
通过前面的分析,我们知道Window和WindowManager是如何关联起来的,然鹅到目前为止,我们还是不知道在Framework层的Window是如何同Native层的WindowManagerService进行通信的,让我们继续看源码。
上面说到,具体对View的操作实际是WindowManagerGlobal这个类来做的,那我们看看addView()方法里做了什么:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略代码
synchronized (mLock) {
// 继续省略代码
// 构建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
// 设置参数
view.setLayoutParams(wparams);
// 把view,root, param添加到对应的list中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 调用setView方法显示View
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
这里主要做了几件事:
- 构建ViewRootImpl对象
- 设置参数
- 添加到list中
- 把View显示出来
我们一个个来看,首先是构建ViewRootImpl对象
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
// 省略代码
}
这里初始化了许多的成员变量,其中有一个是mWindowSession
,我们进去看看
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
// 同步代码块,保证线程安全
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
// 获取WindowManagerService对象
IWindowManager windowManager = getWindowManagerService();
// 建立一个Session
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
看一下WindowManager是如何获取WMS对象的:
@UnsupportedAppUsage
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
通过ServiceManager.getService(“window”)获取到WMS,然后再转为IWindowManager,那么在getService()方法中做了什么呢?
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
可以看到getService()方法返回的是IBinder对象。到这里我们就真正拿到了WMS了,也知道了Framework层与Native底层其实都是通过Binder机制进行通信。
这里比较绕,我们按照流程来捋一遍:首先在ViewRootImpl构建过程中,我们需要初始化IWindowSession对象,因此在getWindowSession() -> getWindowManagerService() -> getService()中从缓存列表(HashMap)获取WMS,然后通过asInterface函数转为WindowManager对象,最后通过openSession()与WMS建立会话,也就是在Framework层和Native层之间建立了连接。
回答:Window,WM和WMS有什么关系
经过前面三个问题的分析,我们应该有比较清晰的脉络了,对于这三者,Window和WM有关联(通过Activity和Dialog,忘记了可以回头看),WM和WMS有关联(通过WM的实现类WindowManagerImp的小弟WindowManagerGlobal)
因此,在Activity或者Dialog创建的时候,其实这三者就已经创建并且相互关联起来了。
我们还没说View到底是怎么显示出来的,这个问题留到最后一步再来解决。
Window有哪些类型
我们现在来填第一个问题时候埋下的坑,关于Window有哪些类型,其实就三种
- System Window
- Sub Window
- Application Window
System Window(系统窗口)
常见的例如Toast,输入法,系统弹出框等等,这部分窗口我们是没有权限创建的。
还记得上面我们列举了一小部分的Flags吗?System Window的type范围是2000以上,下面列举一部分
WindowManager.java:
//系统窗口
public static final int FIRST_SYSTEM_WINDOW = 2000;
//状态栏
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
// 搜索栏
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
// 来电窗口
@Deprecated
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
// 系统提示
@Deprecated
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
Sub Window(子窗口)
所谓子窗口,则是指这个窗口还要有一个父窗口,例如PopupWindow
Sub Window的范围是是1000~1999,由于Sub Window比较少,我就全部列出来了
WindowManager.java
// 子窗口
public static final int FIRST_SUB_WINDOW = 1000;
// 面板窗口
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒体窗口
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
// 应用程序窗口子面板
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
child of its container.
// 对话框
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
// 媒体信息
@UnsupportedAppUsage
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
// 应用程序窗口顶层子面板
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 结束子窗口
public static final int LAST_SUB_WINDOW = 1999;
Application Window(应用程序窗口)
常见的例如Activity,由于比较少,我也全部列举出来了:
WindowManager.java
// 开始应用程序窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
// 程序窗口的base窗口,其他窗口都在它之上
public static final int TYPE_BASE_APPLICATION = 1;
// 普通应用程序窗口
public static final int TYPE_APPLICATION = 2;
// 程序启动窗口
public static final int TYPE_APPLICATION_STARTING = 3;
// 普通应用程序窗口的一种变体,显示应用程序之前等待时的窗口
public static final int TYPE_DRAWN_APPLICATION = 4;
// 结束程序窗口
public static final int LAST_APPLICATION_WINDOW = 99;
回答:Window有哪些类型
三种,分别是系统窗口,子窗口,应用程序窗口,根据Type大小,系统窗口>子窗口>应用窗口,因此系统窗口在最上层,优先级最高。
WindowManager在Activity启动过程中的作用
Activity的attach()方法
关于Activity启动过程我们先忽略,只了解与Window/WindowManager相关的源码
在Activity.attach() 方法中,我们找到了WindowManager的身影
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
// 忽略代码...
// 创建Window
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// 创建WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillOptions(application.getAutofillOptions());
setContentCaptureOptions(application.getContentCaptureOptions());
}
在attach()方法中,Activity会创建一个Window,然后setWindowManager() 我们在上面已经分析过了,其实就是创建了一个WindowManagerImpl类,并把Window和WM关联了起来。
接下来在Activity的onCreate()方法中,我们会调用setContentView()方法:
AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
继续进入AppCompatDelegate.java,这是一个抽象类,没有实现具体方法,我们直接看他的子类AppCompatDelegateImpl.java
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
// 主要关注这个方法
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
@Override
public void setContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v, lp);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
这里提供了三个setContentView的重载方法,我们只看第二个,也就是传入layoutId的这个方法:
ensureSubDecor();
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); // 创建Decor
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true;
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
createSubDecor()
private ViewGroup createSubDecor() {
// 忽略代码...
// 获取Window
ensureWindow();
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 忽略代码...
// 调用PhoneWindow的setContentView方法
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
这个方法主要做了两个事情
- 获取Window,保证mWindow对象不为空
- 获取到Window对象后再调用setContentView()方法
我们先看第一步:
private void ensureWindow() {
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
if (mWindow == null && mHost instanceof Activity) {
// 调用Activity的getWindow()方法
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
// 把上面获取到的window对象赋值给mWindow
private void attachToWindow(@NonNull Window window) {
if (mWindow != null) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
final Window.Callback callback = window.getCallback();
if (callback instanceof AppCompatWindowCallback) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
// Now install the new callback
window.setCallback(mAppCompatWindowCallback);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
mContext, null, sWindowBackgroundStyleable);
final Drawable winBg = a.getDrawableIfKnown(0);
if (winBg != null) {
// Now set the background drawable
window.setBackgroundDrawable(winBg);
}
a.recycle();
mWindow = window;
}
还记得在这个问题的开头,Activity的attach()方法中做了什么吗?
没错,我们初始化了一个PhoneWindow对象,并赋值为了mWindow!因此在这里拿到的Window对象自然也是PhoneWindow了!
因此第二步调用的setContentView()方法,也就是:PhoneWindow.setContentView()了。我们继续往下看
PhoneWindow的setContentView()方法
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
// 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) {
//1.创建DecorView,以及DecorView中的mContentParent 布局
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//2,将layoutResID布局加载到mContentParent和上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//3通知视图改变回调
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我们看一下installDecor()
做了什么
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 创建Decor
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成一个Layout
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
}
具体的源码就不再仔细分析了,在这里系统主要做了:创建Decor,然后根据Window的Flag, Theme等配置创建一个布局,并且添加到Decor中。
至此,Activity已经成功的创建了WindowManager, 创建了Decor,创建了相应的布局,还差最后一步:把这个布局添加到Window中并显示出来。
让我们回到最初的起点,也就是ActivityThread,在这里我们能看到有个方法:handleResumeActivity()并找到其中一段
// 设置activity为可见状态
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
// 注意这个调用,正是因为它用户才能看到activity的界面内容
mDecor.setVisibility(View.VISIBLE);
}
在这里系统又做了三件事情:
- 获取WindowManager
- 把刚刚创建的Decor对象添加到WM中
- 设置Decor为可见状态
最终Activity才能正常显示在用户的面前。
回答:WindowManager在Activity启动过程中的作用
首先ActivityThread.attach()方法会创建Window和WindowManager
然后在onCreate()方法中会调用创建的PhoneWindow的setContentView()方法,接着在里面创建Decor以及一个根布局,创建完毕后把layoutResId加载到布局中,然后通知回调
最后调用ActivityThead.handleResumeActivity()方法,在这里把WindowManager和Decor关联起来,并且调用setVisibility让Decor可见,最终把UI呈现在用户面前。
WindowManager如何处理View的添加、移除和更新
这一个问题其实是上一个问题的延伸和扩展,虽然在上面我们说到了ActivityThread()调用handleResumeActivity()方法,然后把Decor通过addView()的方式加入到WindowManager中,但是我们有没有想过,这个addView()的过程有涉及到哪些模块呢?
正是因为这一过程比较复杂,因此也值得单独提出来探究。
addView() 过程
还记得WindowManager.addView() 实际调用的是哪个类的方法吗?忘了的请回去重新看一遍第二问~
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略N行代码...
try {
// 调用ViewRootImpl.setView方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
RootViewImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 1
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 2
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
}
}
RootViewImpl.setView()方法很复杂,其中我们需要关注的是两点:
- requestLayout()
- mWindowSession.addToDisplay()
我们先看一下requestLayout()方法:
// 定义 TraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 初始化 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); // 检查锁
mLayoutRequested = true;
scheduleTraversals(); // 发送 CALLBACK_TRAVERSAL 消息
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 向 mTraversalRunnable 发送一条 CALLBACK_TRAVERSAL 消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
往Handler里面发送一条CALLBACK_TRAVERSAL消息,这条消息的意思就是刷新界面。
最终会调用 doTraversal() 方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals(); //1
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
然后到 performTraversals() 方法, 这个方法非常复杂,整个方法加起来大概有800多行,主要工作就是
- 测量各个View的大小(performMeasure)
- 布局(performLayout)
- 绘制(performDraw)
最终把整个视图树展示出来
updateViewLayout() 过程
update过程比较简单,直接上源码:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index); // 1
mParams.remove(index); // 2
mParams.add(index, wparams); // 3
root.setLayoutParams(wparams, false); // 4
}
}
- 首先获取要update的View的ViewRootImpl
- 把这个View的LayoutParam移除掉
- 重新添加LayoutParam
- 刷新根布局
removeView() 过程
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate); // 1
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
// 移除对应的View
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate); // 2
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie(); // 3
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE); // 5
return true;
}
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
// 省略代码...
WindowManagerGlobal.getInstance().doRemoveView(this); //4
- 首先通过removeView()方法来移除View,在这个方法里面调用了removeViewLocked()
- 在removeViewLocked()又调用了 root.die(immediate)
- 在die() 又调用了 doDie()
- 在doDie()中调用了 doRemoveView()
- 如果需要延迟,则再发送一条MSG_DIE,重新调用doDie()方法
doRemoveView()
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
这个方法和上面的update方法类似,从缓存列表中找到对应的view,然后移除掉。
总结
在这篇文章中,我们从Acitivty的创建过程说起,涉及到ActivityThread, Window, WindowManager, WindowManagerService, RootViewImpl, PhoneWindow, WindowManagerImpl, WindowManagerGlobal 这么多类的相关源码分析,希望大家看完之后能对WindowManager相关的知识点有所了解。
如果文章有不对的地方也欢迎大家批评指正,感谢阅读!