Window的分类
- 系统级Window : z-ordered为 2000-2999
- 应用层Window : z-ordered为 1-99
- 子Window : z-ordered为 1000-1999
根据下面谷歌的Android层级图可以看出来WindowManager是在framework层掌管Window的
从代码上看WindowManager是一个接口,此接口继承自ViewManager
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
int DOCKED_INVALID = -1;
int DOCKED_LEFT = 1;
int DOCKED_TOP = 2;
int DOCKED_RIGHT = 3;
int DOCKED_BOTTOM = 4;
final static String INPUT_CONSUMER_PIP = "pip_input_consumer";
final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";
public static class BadTokenException extends RuntimeException {
public BadTokenException() {
}
public BadTokenException(String name) {
super(name);
}
}
public static class InvalidDisplayException extends RuntimeException {
public InvalidDisplayException() {
}
public InvalidDisplayException(String name) {
super(name);
}
}
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public interface KeyboardShortcutsReceiver {
void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
}
final int TAKE_SCREENSHOT_FULLSCREEN = 1;
final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
@SystemApi
@RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
public Region getCurrentImeTouchRegion();
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
...
}
看上面的结构WindowManager内部定义了一些值来定位上下左右的方向,同时又一个LayoutParams
WindowManager的实现者是谁呢?
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@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);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
@Override
public void requestAppKeyboardShortcuts(
final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
List<KeyboardShortcutGroup> result =
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
receiver.onKeyboardShortcutsReceived(result);
}
};
try {
WindowManagerGlobal.getWindowManagerService()
.requestAppKeyboardShortcuts(resultReceiver, deviceId);
} catch (RemoteException e) {
}
}
@Override
public Display getDefaultDisplay() {
return mContext.getDisplay();
}
@Override
public Region getCurrentImeTouchRegion() {
try {
return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion();
} catch (RemoteException e) {
}
return null;
}
}
- 实现ViewManager的接口通过WindowManagerGlobal进行代理调用,关于得到Display通过Context代理。
- 在构造中传递了两个参数一个是Context我们知道是用于Dispalay的得到,另一个是Window对象看名字是父window,但是在addView和updateViewLayout中调用applyDefaultToken时进行判断要求令牌不能为null并且父Window必须是null此时才将传递进来的
ViewGroup.LayoutParams params
转化成WindowManager.LayoutParams
并且给wparams.token赋值令牌。这说明了只有当令牌不为空,没有父window也就是说必须是子Window才有令牌。
由于上面的信息我们引出了WindowManagerGlobal,由下面代码我们可以知道WindowManagerGlobal是单例,也就是说当前进程的内存中只有一份,也变相说明了就算是持有多个WindowManagerImpl也只要通过进程内唯一的WindowManagerGlobal进行add,remove等操作View,可想而知framework层是对View有个统一管理的,现在我们需要看WindowManagerGlobal内部怎么做。
private WindowManagerGlobal() {
}
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
我们通过接口进行addView的代码是:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);//决定给params.token设没设置令牌
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
通过传入view,params和通过context得到的Display对象的引用以及父窗口的Window对象。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...//进行参数的判空
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//如果父Window不为null则进行调整参数
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// 如果不是父window则从应用的硬件加速设置中对应到视图的硬件加速中
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) {
//开始监视系统属性的更改
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);
}
//从mViews列表中找到对应的view
int index = findViewLocked(view, false);
if (index >= 0) {
//如果消亡的列表中有此View,则调用对应ViewRootImpl的doDie进行销毁通知操作
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
} else {
//同一个View不能添加两次
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
//如果视图级别在1000-1999之间的话,并且令牌等于当前传递进来View的令牌则把对应view找出来设置成panelParentView
//说明令牌和级别掌管着子view的父子关系
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);
}
}
}
//通过display信息和context得到一个ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//给添加的View设置params
view.setLayoutParams(wparams);
//将view带来的参数添加到如些列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//给ViewRootImpl设置view,参数以及父面板view
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
WindowManagerImpl.addView的操作主要是对view的管理,将添加到的view和此view带来的一些参数添加到内部列表中,其中过程中生成ViewRootImpl才是关键。每一个添加的View都对应一个ViewRootImpl对象。
ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();//得到WMS中对应通信的Session通信Binder类
mDisplay = display;
mBasePackageName = context.getBasePackageName();//得到包名
mThread = Thread.currentThread();//得到当前线程
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);//生成一个W类
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
//通过session,W,dispaly等信息起到信息汇总收集总用
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(mAccessibilityInteractionConnectionManager, mHandler);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(mHighContrastTextManager, mHandler);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
loadSystemProperties();
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
}
}
}
所以当add一次view的时候就会触发requestLayout方法
@Override
public void requestLayout() {
//初始为false
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;//防止重复绘制
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals这个才是核心方法
private void performTraversals() {
......
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
//执行绘制操作
performDraw();
}
对应以下:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......
}
private void performDraw() {
......
draw(fullRedrawNeeded);
......
}
最后分别抵达用户的onMesure,onLayout,onDraw
最后一个问题,我们从应用层已经知道尾了,我们如何知道头呢?也就是从哪里调用的addView呢?
这个起源对于Activity来说来自于Activity.attach方法生成了PhoneWindow,如果你还想追溯上去,那就了解Activity的创建过程来源于ActivityThread。ActivityThread会调用handleResumeActivity代表要显示这个Activity的界面了。所以attach调用在handleResumeActivity之前就得生成DecorView
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
..................
if (r.window == null && !a.mFinished && willBeVisible) {
//获得当前Activity的PhoneWindow对象
r.window = r.activity.getWindow();
//获得当前phoneWindow内部类DecorView对象
View decor = r.window.getDecorView();
//设置窗口顶层视图DecorView可见度
decor.setVisibility(View.INVISIBLE);
//得当当前Activity的WindowManagerImpl对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
//标记根布局DecorView已经添加到窗口
a.mWindowAdded = true;
//将根布局DecorView添加到当前Activity的窗口上面
wm.addView(decor, l);
.....................