内容简介:Window是一个抽象概念,它是以View的形式存在,每个Window都对应着View和ViewRootImpl,Window和View之间通过ViewRootImpl建立联系Window的整个添加过程可分为两部分执行:View的添加是从调用windowManager.addView()开始的,其实点开windowManager只是一个继承ViewManager的接口,在活动中真正执行任务的是它的实现类WindowMangerImpl,因此方法会执行到WindowMangerImpl.addView(),但
val layoutParams = WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE shl WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL shl WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED windowManager.addView(imageView,layoutParams) 复制代码
- Window类型
- 系统级WIndow:系统级别的Window需要声明权限才能创建,如Toast
- 应用级Window:系统的活动窗口,如:Activity
- 子Window:附属在父Window中,如:Dialog
- FLAG
- FLAG_NOT_FOCUSABLE:表示不获取焦点,事件最终会传递到底层的Window
- FLAG_NOT_TOUCH_MODEL:只处理自己区域内的点击事件,区域之外的传递给底层Window
- FLAG_SHOW_WHEN_LOCKED:开启此模式可以显示在锁屏上
2、Window工作过程
Window是一个抽象概念,它是以View的形式存在,每个Window都对应着View和ViewRootImpl,Window和View之间通过ViewRootImpl建立联系
2.1、Window的添加过程
Window的整个添加过程可分为两部分执行:
- WindowManager
- WindowManagerService
View的添加是从调用windowManager.addView()开始的,其实点开windowManager只是一个继承ViewManager的接口,在活动中真正执行任务的是它的实现类WindowMangerImpl,因此方法会执行到WindowMangerImpl.addView(),但WindowMangerImpl 是个聪明的类,在addView()中除了验证设置LayoutParams的合法性之外,它又将所有的工作都桥接给WindowManagerGlobal执行:
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params);//验证params的合法性 mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); // 直接交给WindowManagerGlobal处理 } 复制代码
- WindowManagerGlobal
在具体执行方法前先介绍下WindowManagerGlobal中的各个集合的作用(见下面注释),在Window工作的整个过程他们时刻保存着Window和View的运行状态
private final ArrayList<View> mViews = new ArrayList<View>(); // 保存所有Window对应的View private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // 保存所有Window对应的ViewRootImpl private final ArrayList<WindowManager.LayoutParams> mParams = // 保存所有Window对应的WindowManager.LayoutParams new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 保存正在被删除的View 复制代码
- WindowManagerGlobal.addView()
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); root.setView(view, wparams, panelParentView); // View的绘制 复制代码
上面是addView()中的部分代码,它执行了以下几个操作:
- 创建ViewRootImpl的实例
- 设置View的布局参数
- 分别在集合中保存view、root和params
在保存了相关数据后,View真正的执行就是setView()这一句开始,下面看看ViewRootImpl中是如何实现View的测量绘制的
- ViewRootImpl.setView()
ViewRootImpl是View中的最高层级,属于所有View的根(但ViewRootImpl不是View,只是实现了ViewParent接口),实现了View和WindowManager之间的通信协议
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { ...... requestLayout();//对View进行第一次测量和绘制 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); //调用WindowSession的addTodiaplay()添加窗口 } } 复制代码
requestLayout()内调用scheduleTraversals(),scheduleTraversals()中 会获取主线程的Handler然后发送消息执行TraversalRunnable实例,TraversalRunnable是Runnable的实现类,在run()方法中执行oTraversal() ,然后方法会执行到performTraversals()
- performTraversals()
//调用performMeasure完成Window内视图的测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false; if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } ...... final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, mWidth, mHeight); //完成View的布局Layout } ...... performDraw();//对View的绘制 复制代码
performTraversals方法中,依次调用了performMeasure、performLayout、performDraw三个方法,这三个方法中又分别调用View或ViewGroupde的measure、layout和draw方法,完成了View的测量、布局和绘制;
- WindowSession使用Binder机制调用IWindowSession接口,内部调用WindowManagerService.addWindow()添加,到此所有的操作就执行到了WindowManagerService中,关于WindowManagerService的工作过程请参考Android窗口管理分析
@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); } 复制代码
2.2、Window删除过程
删除过程和添加一样最后执行任务的都是WindowManagerGlobal,先看下WindowManagerGlobal的removeView()方法:
int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); 复制代码
removeView()中主要执行三个步骤:
- 获取当前操作View的index
- 获取mRoots中保存的ViewRootImpl实例
- 调用removeViewLocked执行删除
- removeViewLocked():获取要删除的View执行删除操作
ViewRootImpl root = mRoots.get(index); View view = root.getView(); // 获取ViewRootImpl保存的View boolean deferred = root.die(immediate); // 调用die()执行删除View mDyingViews.add(view); // 将要删除的View添加到mDyingViews 复制代码
- die():发送删除消息
boolean die(boolean immediate) { if (immediate && !mIsInTraversal) { doDie(); return false; } mHandler.sendEmptyMessage(MSG_DIE); return true; } 复制代码
在die()方法中根据传入的immediate执行同步或异步删除:
- 同步删除:直接调用doDie()方法执行删除
- 异步删除:发送Handler消息调用doDie()
- doDie():真正执行View的删除
mView.dispatchDetachedFromWindow(); mWindowSession.remove(mWindow); mView.onDetachedFromWindow(); WindowManagerGlobal.getInstance().doRemoveView(this); 复制代码
doDie是真正发起删除的地方,执行操作如下:
- 调用用mWindowSession最终调用WindowMangerService.removeWindow()
- 调用View的onDetachedFromWindow()执行View的移除操作
- 移除mRoots、mParams、mDyingView中保存的View信息
2.3、Window更新过程
- WindowManagerGlobal.updateViewLayout()
public void updateViewLayout(View view, ViewGroup.LayoutParams params) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); // 设置新的LyaoutParams synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); //更新root,mParams集合中的数据 mParams.remove(index); mParams.add(index, wparams);//替换mParams中保存的wparams root.setLayoutParams(wparams, false); // 更新View } } 复制代码
执行过程见上面注释,在 root.setLayoutParams中会触发ViewRootImpl的scheduleTraversals实现View的测量、布局、绘制;
3、实例
3.1、Activity中Window的添加
借用网络上的一幅图展示Activity的层次关系:
- PhoneWindow:Activity的活动窗口
- DecorView:所有视图的根View,其中包含标题栏和content
- ContentView:布局容器设置的layout文件被加载到其中
- window的创建
- 在Activity.attach()方法中使用PolicyManager.makeNewWindow()创建PhoneWIndow
mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); 复制代码
- window设置视图
//Activity中 public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); //调用PhoneWindow的setContentView() initWindowDecorActionBar(); } 复制代码
此处的getWindow()得到的就是前面创建的PhoneWindow ,所以setContentView()最终是在PhoneWindow中执行的
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } } 复制代码
setContentView()方法中,首先判断contentParent是否空,如果为空则执行installDecor(),installDecor()中有两处代码比较明显,分别是初始化DecorView和mContentParent,下面分别看看这两个方法
- generateDecor():创建DecorView实例
protected DecorView generateDecor(int featureId) { return new DecorView(context, featureId, this, getAttributes()); //初始化DecorView,此时只是一个FrameLayout } 复制代码
- generateLayout():加载布局文件并初始化mContentParent
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //根据加载后的布局查找content ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 加载DecorView布局中的content容器 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; //content的id //mDecor.onResourcesLoaded() final View root = inflater.inflate(layoutResource, null); //加载原始布局文件:包含标题栏和content addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); //将加载的布局添加到DecorView中 复制代码
在generateLayout中完成了布局layout文件的加载,具体细节如下:
- 加载getWindowStyle中的属性值
- 根据设置的style初始化Layout的WindowManager.LayoutParams 和选择系统的布局资源layoutResource
- 设置DecorView的背景、标题、颜色等状态
- 然后调用mDecor.onResourcesLoaded()加载layoutResource到DecorView中
- 根据资源id获取布局中的contentParent容器
- 将View添加到DecorView的contentParent容器中
@Override public void setContentView(int layoutResID) { mLayoutInflater.inflate(layoutResID, mContentParent); //加载布局到mContentParent中 } 复制代码
- 加载完布局后回调onContentChange(),通知Activity加载完毕
final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } 复制代码
到此DecorView和contentParent初始化已经完成,DecorView中加载了一个具有TitleView和ContentView的布局,并且加载的layoutResID也已加载到ContentView中,所以关于DecorView内部的工作已经完成,但DecorView未被添加到Window中,所以此时界面仍是不可见
- DecorView添加到Window()
ActivityThread的handleResumeActivity()中调用Activity的makeVisible()方法,makeVisible中调用WindowManager.addView()将DecorView添加到PhoneWindow中,到此布局资源展示在屏幕上
//handleResumeActivity if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); //将DecorView添加到PhoneWindow中 mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); //设置DecorView可见 } 复制代码
3.2、Dialog中Window的添加
- Dialog使用
val dialog = Dialog(this,R.style.Base_ThemeOverlay_AppCompat_Dialog) dialog.setContentView(R.layout.dialog) dialog.show() dialog.cancel() 复制代码
- 创建Window
从上面使用可以看出,dialog设置布局时和Activity都是使用setContentView,所以其执行初始化的过程和Activity一致,只是在将DecorView添加到Window时有所不同
- 将DecorView添加到Window
public void show() { //在Dialog显示时添加到Window中 mWindowManager.addView(mDecor, l); // 添加DecorView } 复制代码
- dialog关闭时通过WindowManager移除DectorView
mWindowManager.removeViewImmediate(mDecor); 复制代码
3.3、Toast中Window的创建
- 使用
Toast.makeText(this,"Toast",Toast.LENGTH_SHORT).show() 复制代码
- makeText():makeText执行了Toast的文件加载和和设置
Toast result = new Toast(context, looper); //创建Toast实例,并传入队列Loop LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); //加载Toast布局并设置View TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); //设置Toast的信息 result.mNextView = v; // 复制给mNextView result.mDuration = duration; //设置Toast的弹出时长 return result; 复制代码
- Toast的显示
INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; service.enqueueToast(pkg, tn, mDuration); 复制代码
针对上面方法中做几点说明:
- service是INotificationManager的代理类,此处是IPC通信;
- TN 是ITransientNotification的代理类
- mNextView是本次Toast加载的View
- service.enqueueToast()将Toast加入消息队列
Toast最终回调TN中的show方法,show()中发送Message到Handle,然后调用handleShow()
public void handleShow(IBinder windowToken) { handleHide(); mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; mParams.hideTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; mParams.token = windowToken; mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mWM.addView(mView, mParams); } 复制代码
handleShow()中执行以下操作:
- 调用handleHide()隐藏前一个Toast
- 设置Toast的mParams参数,如:坐标、mDuration
- 调用WindowManager的addView()添加View
WindowManagerService是如何执行Window的添加和操作的?
以上所述就是小编给大家介绍的《Android知识进阶树——Window & ViewRootImpl》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 肖仰华谈知识图谱:知识将比数据更重要,得知识者得天下
- 基础知识:css3核心知识整理
- 从知识工程到知识图谱全面回顾 | AI&Society
- 知识图谱发展的难点&构建行业知识图谱的重要性
- 《面试知识,工作可待:集合篇》:Java 集合面试知识大全
- 第四期知识与认知图谱:神经机器翻译也应该嵌入「知识」
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。