Android—Window、WindowManage、屏幕绘制及刷新
Activity窗口层级:
所以在onCreate方法体中setContentView方法都是设置DecorView的ContentView。
Window、PhoneWindow、DecorView的关系:
public abstract class Window {...@Nullablepublic View findViewById(@IdRes int id) {return getDecorView().findViewById(id);}public abstract void setContentView(@LayoutRes int layoutResID);...
}
Window是一个抽象基类,它提供了一系列窗口的方法,比如设置背景,标题等等,而它的唯一实现类则是PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {private final static String TAG = "PhoneWindow";...// This is the top-level view of the window, containing the window decor.private DecorView mDecor;// This is the view in which the window contents are placed. It is either// mDecor itself, or a child of mDecor where the contents go.private ViewGroup mContentParent;private ViewGroup mContentRoot;...
}
在PhoneWindow里面,出现了成员变量DecorView的而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承FrameLayout,所以DecorView是一个FrameLayout窗口。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {/* package */int mDefaultOpacity = PixelFormat.OPAQUE;/** The feature ID of the panel, or -1 if this is the application's DecorView */private final int mFeatureId;private final Rect mDrawingBounds = new Rect();private final Rect mBackgroundPadding = new Rect();private final Rect mFramePadding = new Rect();private final Rect mFrameOffsets = new Rect();....}
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);
}
ViewManager接口定义了add、update、remove方法操作View。
实现类:
ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {private static final String TAG = "ViewGroup";...public void addView(View child, LayoutParams params) {addView(child, -1, params);}/** @param child the child view to add* @param index the position at which to add the child or -1 to add last* @param params the layout parameters to set on the child*/public void addView(View child, int index, LayoutParams params) {// addViewInner() will call child.requestLayout() when setting the new LayoutParams// therefore, we call requestLayout() on ourselves before, so that the child's request// will be blocked at our levelrequestLayout();invalidate(true);addViewInner(child, index, params, false);}...
ViewGroup里面实现了ViewManager接口,View通过ViewGroup的addView方法添加到ViewGroup中,而ViewGroup层层嵌套到最顶级都会显在一个窗口Window中。
WindowManager
/* The interface that apps use to talk to the window manager.
Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.
*/
public interface WindowManager extends ViewManager {public static class BadTokenException extends RuntimeException{...}public static class InvalidDisplayException extends RuntimeException{...}public Display getDefaultDisplay();public void removeViewImmediate(View view);public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable
可以看到WindowManager是一个接口,而且它继承与ViewManager。WindowManager字面理解就是窗口管理器,每一个窗口管理器都与一个的窗口显示绑定。获取实例可以通过Context.getSystemService(Context.WINDOW_SERVICE)获取。既然继承了ViewManager,那么它也就可以进行添加删除View的操作了,不过它的操作放在它的实现类WindowManagerImpl里面。
public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Display mDisplay;private final Window mParentWindow;@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mDisplay, mParentWindow);}...@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}
}
WindowManagerImpl的方法实现又是调用了WindowManagerGlobal的方法。所以WindowManager最后方法实现是在WindowManagerGlobal里面。
一个WindowManager管理多个Window,那么DecorView与WM的绑定是在什么时候?
activity的启动主要是ActivityThread.attach()方法,attach方法最后调用handleBindApplication(),通过mInstrumentation运行onCreate方法,之后调用onStart,onRestoreInstanceState。
private void handleBindApplication(AppBindData data) {//创建appContext final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);try {// If the app is being launched for full backup or restore, bring it up in// a restricted environment with the base application class.//通过反射创建Applicationapp = data.info.makeApplication(data.restrictedBackupMode, null);mInitialApplication = app;try {//调用Application的onCreate方法mInstrumentation.callApplicationOnCreate(app);} catch (Exception e) {}}
}
跳过Activity的onCreate,onStart方法以及onRestoreInstanceState恢复数据方法,直接到handleResumeActivity方法就是onResume。
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {//调用activity.onResume,把activity数据记录更新到ActivityClientRecordActivityClientRecord r = performResumeActivity(token, clearHide);.............if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow(); //赋值View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);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) {a.mWindowAdded = true;//decorView添加进windowmanager,把当前的DecorView与WindowManager绑定一起wm.addView(decor, l);}.....}
所以DecorView与WindowManager在onResume之后绑定的,这也解释了在onCreate、onStart、onResume中不能获取View宽高的原因。ViewRootImpl 还未创建,就不存在渲染操作,也就不存在View的测量步骤了。
addView其实是上面WindowManagerGlobal的方法。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {...ViewRootImpl root;View panelParentView = null;...root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);//ViewRootImpl开始绘制viewroot.setView(view, wparams, panelParentView);...}
ViewRootImpl,它代表了View树的根,每个View 的刷新,绘制,点击事件的分发其实都是由 ViewRootImpl 作为发起者的,由 ViewRootImpl 控制这些操作从 DecorView 开始遍历 View 树去分发处理。
我们看下前面调用到了viewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {...// Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system.//绘制viewrequestLayout(); ...//将DecorView与ViewRootImpl绑定。view.assignParent(this);}@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;//重点scheduleTraversals();}}void assignParent(ViewParent parent) {if (mParent == null) {mParent = parent;} else if (parent == null) {mParent = null;} else {throw new RuntimeException("view " + this + " being added, but"+ " it already has a parent");}}
view.assignParent(this)将ViewRootImpl设为decorView的parent。
requestLayout里调用了scheduleTraversals()方法。这个方法是渲染屏幕的关键方法,由它来发起一次绘制View树的任务请求,invalidate()和postInvalidate()里也调用了这个方法。
ViewRootImpl 是实现了 ViewParent 接口的,所以assignParent方法就将 DecorView 和 ViewRootImpl 绑定起来了。每个Activity 的根布局都是 DecorView,而 DecorView 的 parent 又是 ViewRootImpl。
上面是ViewRootImpl的requestLayout方法,下面看下View的requestLayout方法。
public void requestLayout() {if (mMeasureCache != null) mMeasureCache.clear();if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {// Only trigger request-during-layout logic if this is the view requesting it,// not the views in its parent hierarchyViewRootImpl viewRoot = getViewRootImpl();if (viewRoot != null && viewRoot.isInLayout()) {if (!viewRoot.requestLayoutDuringLayout(this)) {return;}}mAttachInfo.mViewRequestingLayout = this;}//为当前view设置标记位 PFLAG_FORCE_LAYOUTmPrivateFlags |= PFLAG_FORCE_LAYOUT;mPrivateFlags |= PFLAG_INVALIDATED;if (mParent != null && !mParent.isLayoutRequested()) {//向父容器请求布局mParent.requestLayout();}if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {mAttachInfo.mViewRequestingLayout = null;}
}
标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,即调用父容器的requestLayout方法,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,因为刚刚把decorView的父亲设为ViewRootImpl,所以事件直到ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。
流程图:
屏幕刷新:16.6ms
Android系统中每隔16.6ms会发送一次VSYNC信号有可能会触发UI的渲染。
一个典型的显示系统中,一般包括CPU、GPU、display三个部分。
- CPU一般负责计算数据,把计算好数据交给GPU。
- GPU会对图形数据进行渲染,渲染好后放到buffer里存起来。
- Display(屏幕或者显示器)负责把buffer里的数据呈现到屏幕上。
在屏幕刷新中,Android系统引入了双缓冲机制。GPU只向Back Buffer中写入绘制数据,Dispaly只读取Frame Buffer的数据。GPU会定期交换Back Buffer和Frame Buffer。交换的频率也是16.6ms,这就与屏幕的刷新频率保持了同步。
双缓冲机制作用:避免GPU写数据时,Display在读取数据从而造成的屏幕闪烁。
上面我们已经看过了requestLayout方法,下面通过invalidate方法,分析刷新View的过程。
//View.class
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {... public void invalidate() {invalidate(true);}//invalidateCache为true表示全部重绘void invalidate(boolean invalidateCache) {invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {...final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);//重点!p.invalidateChild(this, damage);}...}}
}
invalidateInternal方法中通过调用View的父布局invalidateChild方法来请求重绘。View的父布局可能是ViewGroup或者是ViewRootImpl类。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {@Overridepublic final void invalidateChild(View child, final Rect dirty) {ViewParent parent = this;final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {...//这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环do {View view = null;if (parent instanceof View) {view = (View) parent;}if (drawAnimation) {if (view != null) {view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;} else if (parent instanceof ViewRootImpl) {((ViewRootImpl) parent).mIsAnimating = true;}}... //这里调用的是父布局的invalidateChildInParent方法parent = parent.invalidateChildInParent(location, dirty);} while (parent != null);}}
}public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {@Overridepublic void invalidateChild(View child, Rect dirty) {invalidateChildInParent(null, dirty);}
}
所以如果View的父布局是ViewGroup,会进入一个do while循环,最终两个都还是会到ViewRootImpl的invalidateChildInParent方法中。
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();....invalidateRectOnScreen(dirty);return null;} private void invalidateRectOnScreen(Rect dirty) {final Rect localDirty = mDirty;...if (!mWillDrawSoon && (intersected || mIsAnimating)) {//调用scheduleTraversals方法进行绘制scheduleTraversals();}}
}
可以看到同requestLayout方法一样,都是调用到了ViewRootImpl中的scheduleTraversals方法。
//ViewRootImpl.class
void scheduleTraversals() {if (!mTraversalScheduled) {....//关键在这里!!!mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}....}
}
Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。所以我们来看看mTraversalRunnable
//ViewRootImpl.class
final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);....//找到我们的performTraversals方法来,这里就是开始绘制View的地方啦!performTraversals();....}}
小结:当我们调用View的invalidate或requestLayout方法后,View会去不断向上调用父布局的绘制方法并在这个过程中计算需要重绘的区域,最终调用过程会走到ViewRootImpl中,调用的是ViewRootImpl的performTraversals执行重绘操作。
performTraversals方法里面就是执行onMeasure,onLayout,onDraw三部曲了。不过有时可能只需要执行 performDraw() 绘制流程,有时可能只执行performMeasure() 测量和 performLayout()布局流程。
performTraversals() 是在 doTraversal() 中被调用的,而 doTraversal() 又被封装到一个 Runnable 里,那么关键就是这个 Runnable 什么时候被执行了?
postCallback()最后会调用到一个native方法nativeScheduleVsync(mReceiverPtr),这个方法其实相当于向系统订阅一个接受一个Vsync信号。android系统每过16.6ms会发送一个Vsync信号,由下面这个类来接收Vsync信号。
private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {...@Overridepublic void onVsync(long timestampNanos, int builtInDisplayId, int frame) {...mTimestampNanos = timestampNanos;mFrame = frame;Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}
上面这个类中的onVsync()函数就是来回调Vsync信号,这个回调方法里面将这个类自己的run()方法传给mHandler来进行处理,同时将这个消息设为异步消息。
void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {...try {...// Choreographer.CALLBACK_TRAVERSAL这个参数和 mChoreographer.postCallback()里面传入的一致doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);...}...}void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;synchronized (mLock) {...// 取出之前放入mCallbackQueues的mTraversalRunnablecallbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);...// 回调mTraversalRunnable的run函数for (CallbackRecord c = callbacks; c != null; c = c.next) {c.run(frameTimeNanos);}...}}
所以当Vsync信号来时,调用doFrame方法运行performTraversals方法。
总结:
通过WindowManager将DecorView传给ViewRootImpl的setView方法,在setView方法中的performTraversals()方法中分别对View进行measure, layout, draw,通过ViewGroup的类分别对子View依次进行测量,摆放和绘制。
requestlayout和invalidate的区别?
调用View的requestLayout会不断回调直到ViewRootImpl调用它的requestLayout的会触发onMeasure重新测量,并调用布局onLayout重新布局,不一定会调用onDraw,除非显示不一样了,调用顺序是requestLayout ,然后invalidate。
view的invalidate会递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,不会导致onMeasure和onLayout被调用,只有OnDraw会被调用
postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。
画面造成丢帧大体上有两类原因:
- 遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;
- 主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。
View刷新时机:
代码里调用了某个 View 发起的刷新请求,这个重绘工作并不会马上就开始,而是需要等到下一个屏幕刷新信号来的时候才开始。
而只有当界面有刷新的需要时,我们 app 才会在下一个屏幕刷新信号来时,遍历绘制 View 树来重新计算屏幕数据。如果界面没有刷新的需要,一直保持不变时,我们 app 就不会去接收每隔 16.6ms 的屏幕刷新信号事件了,但底层仍然会以这个固定频率来切换每一帧的画面,只是后面这些帧的画面都是相同的而已。
Android—Window、WindowManage、屏幕绘制及刷新相关推荐
- Android 屏幕绘制机制及硬件加速
文章目录 前言 硬件加速 CPU / GPU结构对比 OpenGL Android 的图形组件 画笔 画纸 画板 合成及显示 CPU / GPU 绘制过程 软件绘制 硬件加速 视图构建 额外点 硬件加 ...
- Android高手笔记-屏幕适配 UI优化
Android高手笔记-屏幕适配 & UI优化 屏幕与适配 由于Android碎片化严重,屏幕分辨率千奇百怪,而想要在各种分辨率的设备上显示基本一致的效果,适配成本越来越高: 屏幕适配究其根本 ...
- Android 界面介绍与绘制优化
Andorid用户界面框架 Android的用户界面框架(Android UI Framework)采用MVC(Model-View-Controller)模型,为用户界面提供了处理用户输入的控制器( ...
- android window manager
一.概述 在Android系统中,从设计的角度来看,窗口管理系统是基于C/S模式的.整个窗口系统分为服务端和客户端两大部分,客户端负责请求创建窗口和使用窗口,服务端完成窗口的维护,窗口显示等. 在Cl ...
- Android Window 如何确定大小/onMeasure()多次执行原因
前言 之前系统地分析了View Measure 过程: Android 自定义View之Measure过程 我们知道父布局根据自身和子布局的要求给子布局生成测量模式和测量尺寸,并封装在MeasureS ...
- Android性能优化一绘制原理分析
Android应用启动慢,使用时经常卡顿,是非常影响用户体验的,应该尽量避免出现. 1.卡顿的分类 按照场景分可以分为: UI绘制 绘制 刷新 应用启动 安装启动 冷启动 热启动 页面跳转 页面间切换 ...
- Android进阶知识:绘制流程(上)
1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...
- Android仿苹果版QQ下拉刷新实现(二) ——贝塞尔曲线开发鼻涕下拉粘连效果
前言 接着上一期 Android仿苹果版QQ下拉刷新实现(一) --打造简单平滑的通用下拉刷新控件 的博客开始,同样,在开始前我们先来看一下目标效果: 下面上一下本章需要实现的效果图: 大家看到这个效 ...
- Android仿苹果版QQ下拉刷新实现(三)
前言 第三篇下拉刷新的博客来的稍微有点晚,因为前两篇的博客访问量一直不是很高,所以博主花了点时间修改了整体的Demo效果,处理了很多极端下拉情况下的显示问题,给大家呈现一个完美的下拉刷新控件.因为本文 ...
最新文章
- mysql 备份压缩数据库_备份压缩mysql 数据库
- Apache Hook机制解析(上)——钩子机制的实现
- 异常信息: java.lang.ClassNotFoundException: org.aspec
- 实行计算机分类标识管理的根本目的,会计信息系统试卷A及答案
- 【华为云技术分享】一统江湖大前端DOClever—你的Postman有点Low
- weblogic java vendor_配置jprofiler监控Weblogic-Zee
- java反序列化时区,Jackson使用Java 8将Elasticsearch反序列化为LocalDateTime
- python中yaml模块的使用_详解Python yaml模块
- 二进制“==”: 没有找到接受“Point”类型的左操作数的运算符(或没有可接受的转换)
- git代码管理可视化工具 :Sourcetree(mac环境)
- 关于CBoard的坑
- 国家信息安全等级保护三级认证(三级等保设置以及注意点)
- dBm与功率(w)换算技巧---心算
- Excel的模板导出+背景水印
- pm2部署node项目
- android 文本倒影,Android 生成倒影图片
- Write-Ahead Log(WAL)的工作原理
- JAVA计算机毕业设计二手手机回收平台系统Mybatis+源码+数据库+lw文档+系统+调试部署
- 如何设置多元化的会员制度
- php获取另一个网页内容,php获取网页内容的三种方法
热门文章
- ios mysql 创建不同的用户表_iOS中数据库-创建表-增删改查数据-基础语法
- fruncm server sql 无法生成 线程_MSSQLSERVER启动不了,报SQL Server 无法生成 FRunCM 线程...
- python filestorage对象怎么转化成字符串_Python面试的10个常见问题及答案,检验你的学习成果吧!...
- java找出一组数据缺少最小数组_Java获取一维数组的最小值实现方法
- json反射java对象_Jackson通过反射将Json转化为java对象
- centos php 开启libgdgd_CentOS搭建PHP环境
- 在阿里、腾讯、美团工作有什么区别?
- 参加智能车大赛还是电赛?在做电磁炮中我找到了答案
- mysql一对多代码_MySQL实现一对多查询的代码示例
- 电路常识性概念(3)-TTL与CMOS集成电路