WindowManager 是窗口,它是一个虚拟的概念,它并不是实际存在的,而是依托于 View ,它的实现类 WindowManagerImpl 更像是个代理人一样,管理着view,我们在 Activity 中可以通过 layout 来添加布局,同样,也可以通过 WindowManager 来实现,如下
class TestActivity extends Activity {
private WindowManager mWindowManager;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;// 系统提示window
layoutParams.format = PixelFormat.TRANSLUCENT;// 支持透明
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | //该flags描述的是窗口的模式,是否可以触摸,可以聚焦等
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.width = 100;//窗口的宽和高
layoutParams.height = 100;
layoutParams.x = 500;//窗口位置的偏移量
layoutParams.y = 500;
mButton = new Button(this);
mButton.setText(" button ");
mWindowManager.addView(mButton, layoutParams);
}
@Override
protected void onDestroy() {
super.onDestroy();
mWindowManager.removeView(mButton);//移除窗口
}
}
首先获取系统的窗口管理器,在 Activity 中调用 getSystemService() 方法,实际上调用的是 ContextImpl 中的方法,
ContextImpl:
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry:
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
static {
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx.getDisplay());
}});
...
}
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
通过上面的代码分析,最终获取的是 WindowManagerImpl 对象,可以理解为通过 getSystemService() 方法获取的对象,是单利的,全局只有它一个,这里用的是多态的格式,因为WindowManagerImpl 是隐藏的,它不需要对外暴露细节实现,我们就用 WindowManager 来表示获取了窗口管理器。
WindowLayoutParams.type 设置的是层级,每个 window 都有对应的层级,应用层例如 Activity 里的 window 在 1-99,子系列例如 Dialog 的 window 是在 1000-1999,系统 Toast 等的 window 在 2000-2999 ,层级高的会覆盖层级低的子 window 必须依赖于父 window 存在,例如 Dialog 必须在 Activity 中弹出,Dialog 中的 window 为子 window ,Activity 中的 window 为父 window,注意显示系统级别的 window 需要权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 在配置清单中配置。WindowLayoutparams 的 flags 是决定焦点及输入事件等, FLAG_NOT_FOUCSABLE 窗口不需要获取焦点,也不需要接收各种输入事件,这个比较细致,可以参考文档。width、height 是显示窗口显示内容区域的宽和高;x、y 是指窗口的偏移量,360手机助手悬浮窗,拖动时就是通过它来改变位置的;然后便是new一个view控件,然后把它添加进去,activity 生命周期结束时,把它remove掉。
上面是我们自己写的极端例子,如果是在 Activity 中,可以直接使用 Activity 的Window,通过 getWindowManager() 方法获取得到 WindowManager;Window 是个抽象类,它的实现类是PhoneWindow,它会通过 setWindowManager() 方法生成一个新的 WindowManager 即实现类 WindowManagerImpl,最终调用 addView() 添加控件,
WindowManagerImpl:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
public void removeView(View view) {
mGlobal.removeView(view, false);
}
通过代码可以知道,添加和移除控件最终还是通过 WindowManagerGlobal 来实现的,看它的方法名,它是单利模式,全局只有一个,看看它的 addView() 方法
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
方法可以简化成这个样子,注意看, 创建了 ViewRootImpl 对象,把 view 和 ViewRootImpl 及 WindowManager.LayoutParams 对象添加到对应的集合中,然后是调用 setView() 方法,这个方法是重点
ViewRootImpl:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
...
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
...
} finally {
if (restore) {
attrs.restore();
}
}
...
}
}
重点看这几行代码即可,requestLayout() 是进行 View 的测量绘制, mWindowSession.addToDisplay() 是通过跨进程 IPC 来添加布局,mWindowSession 是 Session 类型,
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);
}
mService 是 WindowManagerService 类型,这里切换到了 WMS 中, 看了看 WindowManagerService 类,也是 Binder 机制,不熟悉它的朋友,可以看看前几篇关于跨进程的文章,重新切回到 requestLayout() 方法中,看看它里面有什么花样
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);
...
}
}
mChoreographer.postCallback() 方法中,传递进去的 mTraversalRunnable 是 Runnable 类型,mChoreographer 中有 Handler,会执行 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
...
}
}
最终会执行到 performTraversals() 方法中
private void performTraversals() {
final View host = mView;
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
getRunQueue().executeActions(mAttachInfo.mHandler);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
performDraw();
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
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) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performDraw() {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
performTraversals() 中重点看这些方法就行了,尤其是 performMeasure()、performLayout()、performDraw() 这三个方法,分别对应根节点 View 的 measure()、layout()、draw()方法,这分别是view的 测量、布局、绘制 的入口,与前面 https://blog.csdn.net/Deaht_Huimie/article/details/88560620 文章相连接。
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
这些方法,看起名字,mTreeObserver 与view的监听有关,view.getViewTreeObserver() 里面可以添加获取焦点、view是否依附于窗口、view测量完成等等监听,就是在performTraversals()里调用的,这里只是举了两个例子。 getRunQueue().executeActions(mAttachInfo.mHandler); 对应的是 view.post() 方法,有兴趣的可以看看源码流程。
同理,mWindowManager.removeView(mButton) 时,会调用到 WindowManagerGlobal 中 removeViewLocked() 方法,
public void removeView(View view, boolean immediate) {
...
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
...
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
...
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
最终还是在 ViewRootImpl 中进行操作
boolean die(boolean immediate) {
...
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
mHandler 中 MSG_DIE 对应的方法是
void doDie() {
checkThread();
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
...
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
...
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
老套路,先检查线程,然后是 dispatchDetachedFromWindow() 会触发 view 离开窗口的回调 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false), mWindowSession是Session 类型,在它内部会触发 WindowManagerService 的 finishDrawingWindow(this, window) 方法,通过 Binder 机制去移除view,最后是在 WindowManagerGlobal 中集合,移除它的相关内容。
再看看 updateViewLayout() 更新位置的方法,
WindowManagerImpl:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
ViewRootImpl:
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
...
scheduleTraversals();
}
}
updateViewLayout() 方法中,会遍历找到该 View 在集合中对应的 ViewRootImpl,然后把它给移除掉,然后重新添加进去新的 ViewRootImpl 值,最后调用 ViewRootImpl 的setLayoutParams() 方法来进行新的绘制 scheduleTraversals() 方法。