全方位来认识WindowManager
Posted 丶笑看退场
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全方位来认识WindowManager相关的知识,希望对你有一定的参考价值。
Window
应该都比较清楚,它是一个抽象类,具体的实现类为PhoneWindow
, 它对View进行管理。WindowManager
是一个接口类,继承自接口ViewManager
,从名称上来看它是用来管理Window
的,它的实现类为WindowManagerImpl
。如果我们想要对Window
进行添加、更新和删除操作就可以使用WindowManger
,WindowManger
会将具体的工作交由WMS来处理,WindowManager
和WMS
通过Binder
来进行跨进程通信。
Window和WindowManager
来实现一个通过WIndowManager
添加Window
的过程:
Button mFloatingButton = new Button(this);
mFloatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.gravity= Gravity.LEFT|Gravity.TOP;
layoutParams.x=100;
layoutParams.y=300;
WindowManager windowManager = getWindowManager();
windowManager.addView(mFloatingButton,layoutParams);
将一个Button添加到屏幕坐标为(100,300)的位置上。关注两个属性Type
和Flag
,这两个参数比较重要。
Type属性
指的是Window的类型,它分为三种,分别是应用Window、子Window和系统Window。
- 应用类Window对应一个Activity。
- 子Window不能单独存在,它需要附属在特定父WIindow之中(Dialog就是一个子Window)
- 系统Window是需要声明权限在能创建的Window(Toast和系统状态栏都是系统Window)
Window类型 | 层级范围 |
---|---|
Window | 1~99 |
子Window | 1000~1999 |
系统WIndow | 2000~2999 |
Flag属性
Window的标志就是Flag, 用于控制Window的显示,同样定义在WindowManager的内部类LaoyoutParams中,来看下几个常用的:
Flag | 描述 |
---|---|
FLAG_ALLOW_LOCK_WHITE_SCREEN_ON | 只要窗口可见,就允许在开启状态的屏幕上锁屏 |
FLAG_NOT_FOCUSABLE | 窗口不能获得输入焦点,设置改标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置 |
FLAG_NOT_TOUCHABLE | 窗口不接收任何触摸事件 |
FLAG_NOT_TOUCH_MODAL | 将该窗口区域外的触摸事件,传递给其他Window,而自己只会处理窗口区域内的触摸事件 |
FLAG_KEEP_SCREEN_ON | 只要窗口可见,屏幕就会一直亮着 |
FLAG_LAYOUT_NO_LIMITS | 允许窗口超过屏幕外 |
FLAG_FULLSCREEN | 隐藏所有的屏幕装饰窗口,比如游戏、播放器中的全屏播放 |
FLAG_SHOW_WHEN_LOCKED | 窗口可以在锁屏窗口之上显示 |
FLAG_IGNORE_CHEEK_PRESSES | 当用户脸贴近屏幕时(比如打电话时),不会响应此事件 |
FLAG_TURN_SCREEN_ON | 窗口显示时将屏幕点亮 |
Window的内部操作
每一个Window都对应一个View和一个ViewRootImpl
,WIndow和View通过ViewRootImpl
来建立联系的,因此Window并不是时机存在的,它是以View的形式存在。WindowManager
对Window进行管理,说到管理那就离不开对Window的添加、更新和删除操作。
//都是针对View进行操作,说明View才是Window存在的实体
public interface ViewManager
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
看下面代码可以发现,WindowMangerImpl
并没有直接实现Window的三大操作,而是全部交给了WindoowManagerGlobal
来处理,下面我们分析都从WindowmanagerGloabal开始分析
。
### WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
@Override
public void removeView(View view)
mGlobal.removeView(view, false);
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
Window的添加过程
在WindowManagerGlobal
的addView
中做了创建ViewRootImpl
并将View添加到列表中的操作,以及通过ViewRootImpl
来更新界面并完成Window的添加过程。
### WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow)
//检查参数是否合法,如果是子Window那么还需要调整一些布局参数
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");
......
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock)
......
//创建ViewRootImpl并将View添加到列表中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//mViews存储的所有Window所对应的View
mViews.add(view);
//mRoots存储的是所有Window所对应的ViewRootImpl
mRoots.add(root);
mParams.add(wparams);
try
//通过ViewRootImpl来更新界面并完成Window的添加过程
root.setView(view, wparams, panelParentView);
catch (RuntimeException e)
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0)
removeViewLocked(index, true);
throw e;
调用了requestLayouot
方法,以及通过WindowSession
最终来完成Window的添加过程,mWIndowSession
的类型是IWindowSession,
是一个Binder对象,真正的实现类是Sessiion
,也就是Window的添加过程是一次IPC调用。
### ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
......
//在添加到窗口管理器之前安排第一个布局,以确保我们在从系统接收任何其他事件之前进行重新布局。
requestLayout();
......
try
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//会通过WindowSession最终来完成Window的添加过程
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
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();
### ViewRootImpl
public void requestLayout()
if (!mHandlingLayoutInLayoutRequest)
checkThread();
mLayoutRequested = true;
//实际是View绘制的入口
scheduleTraversals();
在Session内部会通过WindowManagerService
来实现Windw的添加。
### WindowManagerService
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel)
//交给它来处理
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
addToDisplay
方法内部调用了WMS
的addWindow
方法并将自身也就是Session
传入了进去,每个应用程序都会有一个Session
,WMS
会用ArrayList
来保存起来,这样所有的工作都交给了WMS
来做。
//WMS补充
Window的删除过程
下面再来看下WindowManagerGlobal
的removeView
的实现:
### WindowManagerGlobal
public void removeView(View view, boolean immediate)
if (view == null)
throw new IllegalArgumentException("view must not be null");
synchronized (mLock)
//查找待删除的View的索引
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
//进一步删除
removeViewLocked(index, immediate);
if (curView == view)
return;
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
在WindowManger
中提供了两种删除接口removeView
和removeViewImmediate
,分别标识异步删除和同步删除。主要用到了removeViewLocked
来做进一步删除,具体的删除操作由ViewRoootImpl
的die
方法。
### WindowManagerGlobal
private void removeViewLocked(int index, boolean immediate)
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
......
//通过ViewRoootImpl来完成删除操作
boolean deferred = root.die(immediate);
if (view != null)
view.assignParent(null);
if (deferred)
mDyingViews.add(view);
在die
方法里,如果是同步删除会直接调用doDie()
,如果是异步删除则会发送一个MSG_DIE
的消息。在doDie
方法中,最终调用dispatchDetachedFromWindow()
来真正删除View。
### ViewRootImpl
boolean die(boolean immediate)
// 同步删除直接调用doDie
if (immediate && !mIsInTraversal)
doDie();
return false;
if (!mIsDrawing)
destroyHardwareRenderer();
else
Log.e(mTag, "Attempting to destroy the window while drawing!\\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
//异步删除户发送一个MSG_DIE的消息,还是会调用doDie()
mHandler.sendEmptyMessage(MSG_DIE);
return true;
### ViewRootImpl
//两个最终都会调用doDie方法
void doDie()
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this)
if (mRemoved)
return;
mRemoved = true;
if (mAdded)
//真正删除View的逻辑是在它的内部实现
dispatchDetachedFromWindow();
.......
mAdded = false;
WindowManagerGlobal.getInstance().doRemoveView(this);
Window的更新过程
再来看下updateViewLayout
更新操作了什么
### WindowManagerGlobal
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的LayoutParams并替换老的LayooutParams
view.setLayoutParams(wparams);
synchronized (mLock)
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//再更新ViewRootImpl中的LayoutParams
root.setLayoutParams(wparams, false);
ViewRootImpl
的 setLayoutParams(wparams, false)
方法最后会调用ViewRootImpl
的schedultTraversals
方法来对View重新布局,包括测量、布局、重绘这三个操作。同时还会通过WindowSession
来更新Window的视图,这个过程最终是由WindowManagerService
的relayoutWindow
来具体实现。
### ViewRootImpl
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//用于接收显示系统的VSYN新号,再下一帧渲染时控制执行一些操作。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch)
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
Activity的Window创建添加过程
无论哪种窗口,它的添加过程在WMS处理部分基本都是相似的,这里以最典型的应用程序窗口Activity
为例。
Activity
在启动过程中,如果Activity
所在的进程不存在则会创建新的进程,创建新的进程就会运行代表主线程的实例ActivityThread
,过程就不讲了,最终会由ActivityThread
中的preformLaunchActivity()
来完成整个启动过程。
在这个方法内部会通过类加载器创建Activity
的实例对象,并调用attach
方法进行一系列关联操作。
### ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent)
.......
try
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
.......
if (activity != null)
.......
appContext.setOuterContext(activity);
//调用attch方法,绑定window
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
if (customIntent != null)
activity.mIntent = customIntent;
.....
在Activity
的attach
方法里,系统会创建Activity
所属的Window
并为其设置回调接口。由于Activity
实现了Window
的Callback
接口,因此当Window
接收到外界的状态改变就会回调Activity方法。
###Activity.attach
//创建PhoneWindow,唯一实现类
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
//传入window回调,绑定
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED)
mWindow.setSoftInputMode(info.softInputMode);
if (info.uiOptions != 0)
mWindow.setUiOptions(info.uiOptions);
由于Actiivity
的视图调用了setContentView
方法进行展示,我们现在来看下setContentView
方法。
### Activity
//将所有顶级视图添加到活动中
public void setContentView(@LayoutRes int layoutResID)
//具体实现还是交给了window处理
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
具体实现还是交给了window处理,而window的具体实现是phoneWndow,接着再来看下PhoneWindow
的setConotentView
方法。
### PhoneWindow
publ以上是关于全方位来认识WindowManager的主要内容,如果未能解决你的问题,请参考以下文章
使用 WindowManager.addView 添加动态视图
组复制监控 | 全方位认识 MySQL 8.0 Group Replication
组复制安全 | 全方位认识 MySQL 8.0 Group Replication