七. WindowManager对窗口的操作

Window、WindowManager、WMS简单介绍
  • Window
    • 在Android视图体系中,Window就是一个窗口的概念.Android中所有的视图都是依赖于Window显示的.
  • WindowManager
    • 对Window进行管理,包括新增、更新和删除等.
  • WMS
    • 窗口的最终管理者,他负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理的.
常见的Window有哪些?
  • 系统错误窗口
  • Dialog
  • 应用程序窗口
  • PopupWindow
  • Toast
  • 输入法窗口
Window分类
  • Application Window
    • Activity就是一个典型的应用程序窗口
  • Sub Window
    • 子窗口,顾名思义,他不能独立存在,需要附着于其他窗口才可以,PopupWindow就属于子窗口
  • SystemWindow
    • 输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口、Toast窗口、来电窗口

窗口的显示层级关系:
Application Window -> Sub Window -> System Window
系统窗口展示在最上面一层,子窗口展示在中间一层,而应用程序窗口展示在最下面一层.
很多窗口在WindowManager类中都有定义一个int值(比如:TYPE_TOAST的值是2005),这个int值越大,这个窗口显示层级越靠上.

WindowManager对窗口的添加、更新

设计模式使用场景:

WindowManagerWindowManagerImpl之间使用到了桥接设计模式.
RecyclerViewLayoutManager之间同样是用到了桥接设计模式.
Activity的生命周期中的attach方法中用到了模版设计模式.
Context的与实现类之间用到了门面设计模式.
xml布局父控件与子控件之间中用到了组合设计模式.

WindowManager抽象类实现了ViewManager接口,这个接口中包含了三个方法,addView()updateViewLayout()removeView(),也就是说我们对窗口的新增、更新和删除,最终是对View的新增、更新和删除.

  • 添加Window
    • Activity.attach()方法中包含了创建PhoneWindow的代码,并且同时创建了WindowManagerImpl负责维护PhoneWindow中的内容
    • Activity.onCreate()方法中调用setContentView()方法,这个方法内部创建了一个DecorView实例作为PhoneWindow的内容
    • WindowManagerImplDecorView的管理,并且在WindowManagerGlobal中创建一个个ViewRootImpl实例,将ViewRootImplView树进行关联,这样ViewRootImpl就可以指挥View树的具体工作.
  • 更新Window
    • WindowManagerImpl中调用updateViewLayout(decor, params)方法进行View的更新,然后会依次调用如下方法:
    • WindowManagerGlobal.updateViewLayout(view, params);
    • ViewRootImpl.setLayoutParams(wparams, false);
    • ViewRootImpl.scheduleTraversals();
      • 这里是告知Window需要刷新,实际的刷新操作是在ViewRootImpl的performTraversals()
    • ViewRootImpl.performTraversals();
      • 这里会执行View(UI)的刷新
重绘面试题
  1. 连续两次setTextView到底会触发几次UI重绘呢?

一次
因为UI必须至少等待16ms的间隔才会绘制下一帧,所以连续两次setTextView只会触发一次重绘

  1. 为什么Android App的帧率一般是60FPS?

帧率时60fps,也就是说1秒钟(s)会刷新60张画面,1000ms/60约等于16ms,也就是两张画面(两帧)之间的间隔时间大概是16ms.

UI刷新流程之setTextView

-> MainActivity.this.setText(“Hello”);//举例
-> TextView.setText(CharSequence text);
-> TextView.setText(CharSequence text, BufferType type);
-> TextView.setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)
-> TextView.checkForRelayout();
-> TextView.invalidate();
-> TextView.invalidate(boolean invalidateCache);
-> TextView.invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate);
-> ViewGroup.invalidateChild(this, damage);//TextView通知父容器ViewGroup需要刷新:这里调用的接口ViewParent的invalidateChild方法,最终会交给实现类ViewGroup去刷新里面的孩子控件
-> ViewGroup.onDescendantInvalidated(child, child);
-> mParent.onDescendantInvalidated(this, target);//ViewGroup通过一层一层递归调用通知根Root需要刷新:这里会一层一层获取ViewGroup的parent中这个方法,最终会调用到根Root也就是ViewRootImpl的onDescendantInvalidated()方法
-> ViewRootImpl.onDescendantInvalidated(child, child);
-> ViewRootImpl.invalidate();
-> ViewRootImpl.scheduleTraversals();

  • -> Choreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//发送垂直同步信号给显示屏系统
    -> Choreographer.postCallbackDelayed(callbackType, action, token, 0);
    -> Choreographer.postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    -> Choreographer.scheduleFrameLocked(now);
    -> Choreographer.isRunningOnLooperThreadLocked();//请求Vsync垂直同步信号
  • -> TraversalRunnable.run();//接收到垂直同步信号,绘制UI
    -> ViewRootImpl.doTraversal();
    -> ViewRootImpl.performTraversals();
    1. -> measureHierarchy()//预布局
    2. -> relayoutWindow();//通过IWindowSession将窗口布局到WMS上
      • mWindowSession.relayout()
    3. -> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//测量
    4. -> performLayout(lp, mWidth, mHeight);//布局
    5. -> performDraw();//绘制
UI局部重绘

某一个View重新绘制,并不会导致所有的View都进行一次measurelayoutdraw,只是这个待刷新View链路需要调整,剩余的View可能不需要浪费时间再来绘制一遍.

SurfaceFlinger

SurfaceFlinger是整个Android系统进行渲染的核心进程.所有应用的渲染逻辑最终都会来到SurfaceFlinger中进行处理,最终会把处理后的图像数据交给CPU或者GPU进行绘制.

整体流程:

SurfaceFlinger是以生产者以及消费者作为核心思想,把每一个进程作为生产者,生产的图元保存到SurfaceFlinger图元队列中,SurfaceFlinger作为消费者,依照一定的规则把生产者存放到SurfaceFlinger中的队列中的图元进行处理.

Window相关面试题
  1. onResume生命周期中测量的宽高是否有效?

onResume这个生命周期方法其实会存在多次调用的情况,比如:当前App退到后台、或者从另外一个页面回来,都是会再次调用onResume方法的.
第一次调用onResume这个生命周期方法的时候是无法获取到测量的宽高的,因为View的绘制流程是在onResume生命周期方法的后面,在ActivityThread的handleResumeActivity()方法中,先执行的onResume生命周期相关逻辑,代码如下:
performResumeActivity(token, finalStateRequest, reason);
然后才会将DecorView添加到Window中,代码如下:
wm.addView(decor, l);
第二次调用onResume则能获取到测量的宽高,因为这个时候View已经绘制完成.

  1. Activity、Window、View三者的联系和区别?

三者是一个包含关系,Activity中包含了Window,而Window中包含了View.
Activity作为一个页面,里面是没有实际内容的,在Activity的attach方法中会创建我们的PhoneWindow窗体,需要通过PhoneWindow作为载体,在ActivityThread中将DecorView添加到PhoneWindow上面,就实现了将控件展示给用户的目的.

  1. 首次View的绘制流程是在什么时候触发的?

WindowManagerImpl.addView
->WindowManagerGlobal.addView
->ViewRootImpl.setView
->ViewRootImpl.requestLayout
->ViewRootImpl.scheduleTranversal

ViewRootImpl.javarequestLayout()方法源码分析:

	public void requestLayout() {
    
    
       if (!mHandlingLayoutInLayoutRequest) {
    
    
       	   //检测创建ViewRootImpl的线程和刷新View的线程是否是同一个线程,不是则报错
           checkThread();
           mLayoutRequested = true;
           //遍历所有View,绘制所有的View
           scheduleTraversals();
       }
   }
  1. 我们调用invalidate()之后会马上进行屏幕刷新吗?

不会
需要等下一帧消息来的时候才会进行屏幕刷新,每一个画面是每隔一段时间一帧一帧绘制到屏幕上的.

  1. 我们说丢帧是因为主线程做了耗时操作,为什么主线程做了耗时操作就会引起丢帧?

主线程做耗时操作,影响到了绘制,虽然发送了屏障消息,防止后面的消息插入到消息队列;但是因为之前还有插入的消息存在消息队列中,仍然需要取出来刷新屏幕,所以主线程中做了耗时操作仍然会导致丢帧.

猜你喜欢

转载自blog.csdn.net/tangkunTKTK/article/details/130710184