背景

公司主营业务:成都网站设计、网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。创新互联公司是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。创新互联公司推出沽源免费做网站回馈大家。
对于Android开发,在面试的时候,经常会被问到,说一说View的绘制流程?我也经常问面试者,View的绘制流程.
对于3年以上的开发人员来说,就知道onMeasure/onLayout/onDraw基本,知道他们呢是干些什么的,这样就够了吗?
如果你来我们公司,我是你的面试官,可能我会考察你这三年都干了什么,对于View你都知道些什么,会问一些更细节的问题,比如LinearLayout的onMeasure,onLayout过程?他们都是什么时候被发起的,执行顺序是什么?
如果以上问题你都知道,可能你进来我们公司就差不多了(如果需要内推,可以联系我,Android/IOS 岗位都需要),可能我会考察你draw的 canvas是哪里来的,他是怎么被创建显示到屏幕上呢?看看你的深度有多少?
对于现在的移动开发市场逐渐趋向成熟,趋向饱和,很多不缺人的公司,都需要高级程序员.在说大家也都知道,面试要造飞机大炮,进去后拧螺丝,对于一个3年或者5年以上Android开发不稍微了解一些Android深一点的东西,不是很好混.扯了这么多没用的东西,还是回到今天正题,Android的绘图原理浅析.
本文介绍思路
从面试题中几个比较容易问的问题,逐层深入,直至屏幕的绘图原理.
在讲Android的绘图原理前,先介绍一下Android中View的基本工作原理,本文暂不介绍事件的传递流程。
View 绘制工作原理
我们先理解几个重要的类,也是在面试中经常问到的
Activity,Window(PhoneWindow),DecorView之间的关系
理解他们三者的关系,我们直接看代码吧,先从Activity开始的setContentView开始(注:代码删除了一些不是本次分析流程的代码,以免篇幅过长)
- //Activity
 - /**
 - * Set the activity content from a layout resource. The resource will be
 - * inflated, adding all top-level views to the activity.
 - *
 - * @param layoutResID Resource ID to be inflated.
 - *
 - * @see #setContentView(android.view.View)
 - * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
 - */
 - public void setContentView(@LayoutRes int layoutResID) {
 - getWindow().setContentView(layoutResID);
 - initWindowDecorActionBar();
 - }
 - public Window getWindow() {
 - return mWindow;
 - }
 
里面调用的getWindow的setContentView,这个接下来讲,那么这个mWindow是何时被创建的呢?
- //Activity
 - private Window mWindow;
 - final void attach(Context context, ActivityThread aThread,····) {
 - attachBaseContext(context);
 - mFragments.attachHost(null /*parent*/);
 - mWindow = new PhoneWindow(this, window, activityConfigCallback);
 - }
 
在Activity的attach中创建了PhoneWindow,PhoneWindow是Window的实现类.
继续刚才的setContentView
- //PhoneWindow
 - @Override
 - public void setContentView(int layoutResID) {
 - if (mContentParent == null) {
 - installDecor();
 - } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 - mContentParent.removeAllViews();
 - }
 - if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 - final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
 - getContext());
 - transitionTo(newScene);
 - } else {
 - mLayoutInflater.inflate(layoutResID, mContentParent);
 - }
 - }
 
在setContentView中,如果mContentParent为空,会去调用installDecor,最后将布局infalte到mContentParent.在来看一下installDecor
- //PhoneWindow
 - // 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.
 - ViewGroup mContentParent;
 - private DecorView mDecor;
 - private void installDecor() {
 - mForceDecorInstall = false;
 - if (mDecor == null) {
 - mDecor = generateDecor(-1);
 - } else {
 - mDecor.setWindow(this);
 - }
 - if (mContentParent == null) {
 - mContentParent = generateLayout(mDecor);
 - }
 - }
 - protected DecorView generateDecor(int featureId) {
 - return new DecorView(context, featureId, this, getAttributes());
 - }
 
在installDecor,创建了一个DecorView.看mContentParent的注释我们可以知道,他本身就是mDecor或者是mDecor的contents部分.
综上,我们大概知道了三者的关系,
理解ViewRootImpl,WindowManager,WindowManagerService(WMS)之间的关系
看了上述三者的关系后,我们知道布局最终被添加到了DecorView上.那么DecorView是怎么被添加到系统的Framework层.
当Activity准备好后,最终会调用到Activity中的makeVisible,并通过WindowManager添加View,代码如下
- //Activity
 - void makeVisible() {
 - if (!mWindowAdded) {
 - ViewManager wm = getWindowManager();
 - wm.addView(mDecor, getWindow().getAttributes());
 - mWindowAdded = true;
 - }
 - mDecor.setVisibility(View.VISIBLE);
 - }
 
那他们到底是什么关系呢? (下面提到到客户端服务端是Binder通讯中的客户端服务端概念. )
以下内容是重点需要理解的部分
View的重绘
从上述关系中,ViewRootImpl是用于接收WMS传递来的消息.那么我们来看一下ViewRootImpl里面的几个关于View绘制的代码.
在这里在强调一下,ViewRootImpl 两个重要的内部类
下面看一下ViewRootHandler类.(以View的setVisible为例.)
- // ViewRootHandler(ViewRootImpl的内部类,用于异步消息处理,和Acitivity的启动很像)
 - //第一步 Handler接收W(Binder)传递来的消息
 - @Override
 - public void handleMessage(Message msg) {
 - switch (msg.what) {
 - case MSG_INVALIDATE:
 - ((View) msg.obj).invalidate();
 - break;
 - case MSG_INVALIDATE_RECT:
 - final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
 - info.target.invalidate(info.left, info.top, info.right, info.bottom);
 - info.recycle();
 - break;
 - case MSG_DISPATCH_APP_VISIBILITY://处理Visible
 - handleAppVisibility(msg.arg1 != 0);
 - break;
 - }
 - }
 - void handleAppVisibility(boolean visible) {
 - if (mAppVisible != visible) {
 - mAppVisible = visible;
 - scheduleTraversals();
 - if (!mAppVisible) {
 - WindowManagerGlobal.trimForeground();
 - }
 - }
 - }
 - void scheduleTraversals() {
 - if (!mTraversalScheduled) {
 - mTraversalScheduled = true;
 - mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 - //开启下次刷新,就遍历View树
 - mChoreographer.postCallback(
 - Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 - if (!mUnbufferedInputDispatch) {
 - scheduleConsumeBatchedInput();
 - }
 - notifyRendererOfFramePending();
 - pokeDrawLockIfNeeded();
 - }
 - }
 
看一下mTraversalRunnable
- final class TraversalRunnable implements Runnable {
 - @Override
 - public void run() {
 - doTraversal();
 - }
 - }
 - final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
 - void doTraversal() {
 - if (mTraversalScheduled) {
 - mTraversalScheduled = false;
 - mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 - performTraversals();
 - }
 - }
 
在TraversalRunnable中,执行doTraversal.并在doTraversal执行performTraversals(),是不是看到了我们熟悉的performTraversals()了?是的,在这里才开始View的绘制工作.
在ViewRootImpl中的performTraversals(),这个方法代码很长(大约800行代码),大致流程是
那么是什么导致View的重绘呢?这里总结了3个主要原因
View的绘制流程
在上一小节了,讲述了performTraversals()的是被WMS IPC调用执行的.View的绘制流程一般是
从performTraversals -> performMeasure() -> performLayout() -> performDraw().
下面看一下performMeasure()
- //ViewRootImpl
 - private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
 - if (mView == null) {
 - return;
 - }
 - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
 - try {
 - mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 - } finally {
 - Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 - }
 - }
 - public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 - MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
 - && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
 - final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
 - && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
 - final boolean needsLayout = specChanged
 - && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
 - if (forceLayout || needsLayout) {
 - mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
 - resolveRtlPropertiesIfNeeded();
 - int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
 - if (cacheIndex < 0 || sIgnoreMeasureCache) {
 - //在这里调用了onMeasure 方法
 - onMeasure(widthMeasureSpec, heightMeasureSpec);
 - mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
 - }
 - }
 - }
 
最终调用了View的measure方法,而View中的measure()方法被定义成final类型,保证整个流程的执行.performLayout()和performDraw()也是类似的过程.
而对于程序员,自定义View只需要关注他提供出来几个对应的方法,onMeasure/onLayout/onDraw. 关于这方面知识的网上介绍的资料很多,也可以很容易的看到View及ViewGroup里面的代码,推荐看LinerLayout的源码理解这部分知识,在这里不详细展开.
Android的绘图原理浅析
Android屏幕绘制
关于绘制,就要从performDraw()说起,我们来看一下这个流程到底是怎么绘制的.
- //ViewRootImpl
 - //1
 - private void performDraw() {
 - try {
 - draw(fullRedrawNeeded);
 - } finally {
 - mIsDrawing = false;
 - Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 - }
 - }
 - //2
 - private void draw(boolean fullRedrawNeeded) {
 - Surface surface = mSurface;
 - if (!surface.isValid()) {
 - return;
 - }
 - if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
 - return;
 - }
 - }
 - //3
 - private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
 - boolean scalingRequired, Rect dirty) {
 - Canvas canvas = mSurface.lockCanvas(dirty);
 - }
 
看代码执行流程,1—>2->3, 最终拿到了Java层的canvas,然后进行一系列绘制操作.而canvas是通过Suface.lockCanvas()得到的.
那么Surface又是一个什么呢?在这里Surface只是一个抽象,在APP创建窗口时,会调用WindowManager向WMS服务发起一个请求,携带上surface对象,只有他被分配完一段屏幕缓冲区才能真正对应屏幕上的一个窗口.
来看一下Framework中的绘图架构.更好的理解Surface
Surface本质上仅仅代表了一个平面,绘制不同图案显然是一种操作,而不是一段数据,Android使用了Skia绘图驱动库来进行平面上的绘制,在程序中使用canvas来表示这个功能.
双缓冲技术的介绍
在ViewRootImpl中,我们看到接收到绘制消息后,不是立刻绘制而是调用scheduleTraversals,在scheduleTraversals调用Choreographer.postCallback(),这又是因为什么呢?这其实涉及到屏幕绘制原理(除了Android其他平台也是类似的).
我们都知道显示器以固定的频率刷新,比如 iPhone的 60Hz、iPad Pro的 120Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync),所以 60Hz的屏幕就会一秒内发出 60次这样的信号。
并且一般地来说,计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给 GPU,GPU渲染后放入帧缓冲区,然后视频控制器按照 VSync信号从帧缓冲区取帧数据传递给显示器显示.
但是如果屏幕的缓冲区只有一块,那么这个VSync同步信号发出时, 开始刷新屏幕,那么你看到的屏幕就是一条一条的数据在变化.为了让屏幕看上去是一帧一帧的数据,一般都有两块缓冲区(也被成为双缓冲区).当数据要刷新时,直接替换另一个缓冲区的数据.
双缓冲技术里面,如果不能特定时间刷新完的话(如果60HZ的话,就是16ms内)把这个缓冲区数据刷新完成,屏幕发出VSync同步信号,无法完成两个缓冲区的切换,那么就会造成卡顿现象。
回到scheduleTraversals()上,这个地方就是使用了双缓冲技术(或者三缓冲技术),Choreographer接收VSync的同步信号,当屏幕刷新来时,开始屏幕的刷新操作。
                本文题目:Android绘制原理浅析「干货」
                
                URL地址:http://www.csdahua.cn/qtweb/news24/402224.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网