前言:Window表示一个窗口的概念,在日常的开发过程中我们很少用到它,但是在某些特定的场景下我们会使用到。查看源码我们知道Window是一个抽象类,而它的实现类是PhoneWindow。而PhoneWindow又是在哪里实例化的呢?不明白的同学可以去我这篇文章看看https://blog.csdn.net/qq_27970997/article/details/83658203。那么Window到底是如何工作的呢?这里面就涉及到另外的两个类:WindowManager和WindowManagerService。那么它们之间的关系又是什么样子的呢?
WindowManager是外界访问Window的入口,WindowManagerService是Window的具体实现,WindowManager和WindowManagerService的交互是一个IPC的过程
先来看一段代码,这是ActivityThread$handleResumeActivity()方法里面的一段代码
// 这是ActivityThread$handleResumeActivity()方法里面的一段代码
// 我们一行一行读
if (r.window == null && !a.mFinished && willBeVisible) {
// r代表的是ActivityClientRecord
// ActivityClientRecord 用来保存Activity的信息
// 获取ActivityClientRecord的Window
r.window = r.activity.getWindow();
// 获取ActivityClientRecord的DecorView
View decor = r.window.getDecorView();
// 设置DecorView为不可见
decor.setVisibility(View.INVISIBLE);
// 拿到当前Activity的WindowManager(WindowManager实现了ViewManager接口)
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
// 设置当前Activity的DecorView
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 把DecorView添加到Window(通过WindowManager进行添加)中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
重点看13,14和32行,这里我就不啰嗦了,看我的注释应该就能看懂了。这个代码很好的解释了Window的外界访问入口WindowManager是如何添加控件的。
当然,可以给Window设置Flags属性:举几个常见的属性
1》FLAG_NOT_FOCUSABLE
表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window
2》FLAG_NOT_TOUCH_MODAL
在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。一般来说此标记是开启状态,否则其他Window将无法收到单击事件
3》FLAG_SHOW_WHEN_LOCKED
开启此模式可以让Window显示在锁屏的界面上
因为WindowManager是实现了ViewManager接口,所以我们先来看看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);
}
ViewManager接口提供了3个方法,一个添加视图,一个更新视图,一个移除视图,由此可知,WindowManager也具有这3个方法。需要注意的是,我们操作的对象是View。由此看来,WindowManager操作Window的过程其实是操作Window里面的View的过程。
Window的添加过程需要通过WindowManager的addView来实现,而WindowManager是一个接口,它的真正实现是WindowManagerImpl类
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
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);
}
在WindowManagerImpl类中我们找到对应的addView(),updateViewLayout(),removeView()方法,但是我们发现WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,那么我们接着往下走,看看WindowManagerGlobal里面是怎么进行处理的(以addView()为例)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 如果添加进来的View为空,则直接抛出异常
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// 如果Window不为空,调整一些布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
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:实现View的绘制
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 省略部分代码
// 实例化ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// mViews存储的是所有Window所对应的View
mViews.add(view);
// mRoots存储的是所有Window所对应的ViewRootImpl
mRoots.add(root);
// mParams存储的是所有Window所对应的布局参数
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 开始进行View的绘制
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 省略代码
requestLayout();
// 省略代码
}
}
}
我们知道 requestLayout();其实就是刷新整个页面布局的方法,那么我们来看看 requestLayout();方法里面做了些什么
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
第一步:checkThread()即检查线程。有没有发现这个错误很熟悉,在开发过程中我们都知道不能在子线程中更新UI,现在知道
为什么了吧!
第二步:执行scheduleTraversals()方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
我们关注最后一行
void pokeDrawLockIfNeeded() {
final int displayState = mAttachInfo.mDisplayState;
if (mView != null && mAdded && mTraversalScheduled
&& (displayState == Display.STATE_DOZE
|| displayState == Display.STATE_DOZE_SUSPEND)) {
try {
mWindowSession.pokeDrawLock(mWindow);
} catch (RemoteException ex) {
// System server died, oh well.
}
}
}
最终会执行 mWindowSession.pokeDrawLock(mWindow),其中mWindowSession是IWindowSession,它是一个Binder对象,真正的实现类是Session,具体的IPC的调用过程有兴趣的同学可以自己去研究研究。最终会执行到WindowManagerService里面的pokeDrawLock去执行
@Override
public void pokeDrawLock(IBinder window) {
final long identity = Binder.clearCallingIdentity();
try {
mService.pokeDrawLock(this, window);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
public void pokeDrawLock(Session session, IBinder token) {
synchronized (mWindowMap) {
WindowState window = windowForClientLocked(session, token, false);
if (window != null) {
window.pokeDrawLockLw(mDrawLockTimeoutMillis);
}
}
}
那么Activity是如何创建Window的呢,这就涉及到Activity的启动流程了,具体的我就不再细讲了,不明白的同学可以去我的另外一篇博客去看看,里面有详解https://blog.csdn.net/qq_27970997/article/details/83658203
这里我稍微总结一下:
1.Activity的启动过程很复杂,但是最终会执行到handleLaunchActivity()方法,这个方法里面首先是执行
performLaunchActivity()方法,在performLaunchActivity()方法里面首先通过反射生成Activity的实例
,然后调用生命周期里面的attach()方法,create(),start()方法
2.在attach()方法里面会做以下几个事情:
1》实例化PhoneWindow
2》获取WindowManager并进行设置
3.在onCreate()方法里面调用setContentView()方法,实例化DecorView并将我们设置的布局文件添加到ID为com.android.internal.R.id.content的容器中
4.回到handleLaunchActivity()方法,执行handleResumeActivity()方法,里面有这几行代码
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
DecorView被添加到Window当中,这样我们的整个流程就走完了,界面就显示出来了。