【Android】深入理解Android View的绘制流程

本文详细分析了Android应用中View的绘制流程,从Activity的setContentView开始,经由WindowManager添加到屏幕,再到ViewRootImpl的setView、requestLayout和doTraversal,最后通过Choreographer协调绘制。流程涉及Activity、Window、DecorView、WindowManager、ViewRootImpl、Choreographer、SurfaceFlinger和OpenGLES等关键组件,揭示了Android UI从创建到显示的完整路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

让我们带着几个疑问去分析一下Android View的绘制流程:

  • View是如何被渲染到屏幕中的?
  • Activity、Window、DecorView、WindowManager、ViewRootImpl的作用和关系?
  • View和Surface是什么关系?
  • View和SurfaceFlinger、OpenGL ES是什么关系?

计算机的图像一般是需要经过CPU计算然后交给GPU渲染,GPU渲染完之后放入缓存中,显示器不断从缓存中读取帧数据显示到屏幕上。

熟悉Andriod体系架构的人都知道,Android底层渲染图像的引擎是Skia/OpenGL ES/Vulkan。那么我们经常使用的View是如何工作的呢?没错,View最终也是交给底层渲染引擎的,那么从View到OpenGL ES这中间经历了哪些过程呢?

setContentView()流程

在Activity的onCreate中我们一般会通过setContentView来给Activity设置界面布局。这个时候,Activity是否开始渲染了呢?并没有,setContentView只是构建整个DecorView的树。

//android.app.Activity
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

setContentView是调用Window的setContentView,而PhoneWindow是Window的唯一实现类:

//com.android.internal.policy.PhoneWindow
 @Override
    public void setContentView(int layoutResID) {
         if (mContentParent == null) {
            installDecor();//1,安装DecorView
        } 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);//2,解析layoutResID
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;

1处开始安装DecorView,主要是new一个DecorView,并找到其中id等于content的布局,通过mContentParent引用。我们在xml的写的布局就是添加到这个mContentParent容器中。

//com.android.internal.policy.PhoneWindow
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) { // 一般mDecor非空,Activity onResume之前已经创建了DecorView,下文会分析。
            mDecor = generateDecor(-1);//generateDecor内部会new一个DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
         if (mContentParent == null) {
         	//重点:给mContentParent赋值,
            mContentParent = generateLayout(mDecor);//找到id==content的ViewGroup
            ...
  }
//generateLayout方法的主要逻辑:
 protected ViewGroup generateLayout(DecorView decor) {
    ...
     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
     }
    ...
    return contentParent;
 }

2处通过LayoutInflator解析传入的layoutResID,解析成View并添加到mContentParent中。mContentParent就是我们xml界面的中的id等于content的布局:
在这里插入图片描述
综上分析,setContentView主要完成两个功能:
1、构建DecorView
2、解析自定义的xml布局文件,添加到DecorView的content中。

所以setContentView还没有真正开始渲染图像。

思考:如果我们没有调用setContentView,Activity能正常启动吗?为什么?

WindowManager.addView流程

Android的所有View都是通过WindowManager.addView添加到屏幕中的。那么Activity的DecorView是什么时候被添加到屏幕中的呢?

答案在ActivityThreadhandleResumeActivity方法中:

//android.app.ActivityThread
   @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            
         ...
         //执行Activity的onResume生命周期
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
         ...
            
		if (r.window == null && !a.mFinished && willBeVisible) {
	            r.window = r.activity.getWindow();
	            View decor = r.window.getDecorView();//1、调用了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 (r.mPreserveWindow) {
	                a.mWindowAdded = true;
	                r.mPreserveWindow = false;
	                // Normally the ViewRoot sets up callbacks with the Activity
	                // in addView->ViewRootImpl#setView. If we are instead reusing
	                // the decor view we have to notify the view root that the
	                // callbacks may have changed.
	                ViewRootImpl impl = decor.getViewRootImpl();
	                if (impl != null) {
	                    impl.notifyChildRebuilt();
	                }
	            }
	            if (a.mVisibleFromClient) {
	                if (!a.mWindowAdded) {
	                    a.mWindowAdded = true;
	                    wm.addView(decor, l);//2、开始调用WindowManager.addView将view添加到屏幕
	                } else {
	                    // The activity will get a callback for this {@link LayoutParams} change
	                    // earlier. However, at that time the decor will not be set (this is set
	                    // in this method), so no action will be taken. This call ensures the
	                    // callback occurs with the decor set.
	                    a.onWindowAttributesChanged(l);
	                }
	            }
	
	            // If the window has already been added, but during resume
	            // we started another activity, then don't yet make the
	            // window visible.
	        } else if (!willBeVisible) {
	            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
	            r.hideForNow = true;
	        }
	        
		...省略无关代码

wm.addView才是开始DecorView渲染的入口。而它的触发时机是在Activity的onResume生命周期之后,所以说onResume之后View才会显示在屏幕上,并且渲染完成才可以获取到View的宽度。

主要看下1处调用了window的getDecorView()方法:

//com.android.internal.policy.PhoneWindow
   @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

这里可以看出,即使我们没有调用setContentView,DecorView也会初始化,只是会显示空白页面。

然后我们重点看下2处的代码,通过WindowManager的addView方法将DecorView添加到window中了:wm.addView(decor, l)

继续分析addView之前先梳理一下必要的基本知识。

上面的wm虽然是ViewManager类型的,它实际就是WindowManager。

WindowManager是一个接口,它继承自ViewManager。

public interface WindowManager extends ViewManager {
...
}

可以看到WindowManager的实现类是WindowManagerImpl,后面WindowManager的功能都是靠WindowManagerImpl来实现的。

Window是抽象类,PhoneWindow是它的实现。WindowManager是Window的成员变量,Window和WindowManager都是在Activity的attach方法中初始化的:

//android.app.Activity
  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);//1,初始化window
        
        ...省略无关代码

        mWindow.setWindowManager(  //2、给window初始化windowManager
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();//3、Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
  • 1处开始初始化window,并赋值给Activity的成员变量mWindow
  • 2处给window设置windowManager
  • 3处Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。

然后重点看下setWindowManager方法的实现:

//android.view.Window
   public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

梳理完基本关系,再回头看下wm.addView过程。

//android.view.WindowManagerImpl
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

可以看到wm.addView交给了mGolbal对象。

mGolbal是WindowManagerGlobal类型的全局单例:

public final class WindowManagerImpl implements WindowManager {
   @UnsupportedAppUsage
   private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   ...

继续看WindowManagerGlobal.addView是如何实现的。

//android.view.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);

           // do this last because it fires off messages to start doing things
           try {
               root.setView(view, wparams, panelParentView);
           } catch (RuntimeException e) {
               // BadTokenException or InvalidDisplayException, clean up.
               if (index >= 0) {
                   removeViewLocked(index, true);
               }
               throw e;
           }
       }
   }

可以看到,这里创建了一个ViewRootImpl对象root,并将viewrootwparams保存到了WindowManagerGlobal类的成员变量中。最后调用了ViewRootImpl的setView方法设置视图。这里可以看到,ViewRootImpl并不是进程内单例,每个Activity有一个Window,每个Window都有一个DecorView,且每个Window都要调用addView方法,addView中会创建一个ViewRootImpl实例,因此可以这么认为:每个Activity都对应一个ViewRootImpl实例。ViewRootImpl实际上充当了Window顶级View的管理者,主要管理View的绘制、View的事件分发。(笔者认为应该叫它ViewRootManager比较合适~~)

ViewRootImpl从名字看上去是ViewRoot接口的实现类,但实际上并不存在ViewRoot这个类,ViewRootImpl也没有继承或者实现ViewRoot:

	public final class ViewRootImpl implements ViewParent,
      View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {...}

获取ViewRootImpl一般直接通过View的getViewRootImpl()方法,只不过App不让用:

  /**
  * Gets the view root associated with the View.
 * @return The view root, or null if none.
 * @hide
*/
@UnsupportedAppUsage
public ViewRootImpl getViewRootImpl() {
     if (mAttachInfo != null) {
         return mAttachInfo.mViewRootImpl;
    }
   return null;
}

回到主线,继续跟踪ViewRootImplsetView方法。

//android.view.ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
				//...省略不重要代码
               
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // 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.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

              //...省略不重要代码

                if (view instanceof RootViewSurfaceTaker) {
                    mInputQueueCallback =
                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                }
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

                view.assignParent(this);
                //...省略不重要代码
            }
        }
    }

WMS是Android窗口管理系统,在将View树注册到WMS之前,必须先执行一次layout,WMS除了窗口管理之外,还负责各种事件的派发,所以在向WMS注册前app在确保这棵view树做好了接收事件准备。

ViewRootImpl起到中介的作用,它是View树的管理者,同时也兼任与WMS通信的功能。

mWindowSession.addToDisplay将View的渲染交给了WindowManagerService。

mWindowSession是IWindowSession类型的变量,在服务端的实现类是Session.java,它是一个Binder对象。

  @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
 @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);
    }

可以看到最终是通过WindowManagerService完成了Window的添加。

requestLayout流程

在ViewRootImpl的setView方法有一个requestLayout调用,它就是来绘制UI的。

//android.view.ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
				//...省略不重要代码
                // 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.
                requestLayout();
                //...省略不重要代码
              }
         }

这个requestLayout方法非常关键,它是view重绘的触发点,也是起点。

所有view需要重绘就需调用requestLayout。比如我们通常设置布局参数setLayoutParams就调了requestLayout:

//android.view.View
    public void setLayoutParams(ViewGroup.LayoutParams params) {
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }

而且view的requestLayout里面会调用 mParent.requestLayout();,也就是它将requestLayout委托给了父布局mParent。

ViewGroup同时也是ViewParent的实例,从继承关系可以看出来:

//android.view.ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
}

View的requestLayout方法实现:

//android.view.View
   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 hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

可以看到它是调用了父控件的requestLayout

mParent就是调用ViewGroup的addView方法时候赋值的,也就是将它赋值为this,即当前容器。

//addView内部会调到addViewInner:
private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
            ...省略无关代码
  		 // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }
        ...省略无关代码
  }

经过上文的分析,所有view的最终根节点就是DocorView,也就是requestLayout最终会委托给DocorView的requestLayout,那么DocorView的mParent是谁呢?就是ViewRootImpl.setView方法里赋值的:

//android.view.ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	...
 	view.assignParent(this);
 	...
 }

也就是所有view的requestLayout其实是追溯到了ViewRootImpl的requestLayout方法。ViewRootImpl就是view的根。

下面回到ViewRootImpl类继续分析,看一下它的requestLayout方法实现:

//android.view.ViewRootImpl
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

继续看scheduleTraversals是如何实现的:

//android.view.ViewRootImpl
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

可以看到它先是在主线程的Looper中发了一条屏障消息,然后调用Choreographer类型的变量mChoreographerpostCallback方法发了一个回调,回调的类型是CALLBACK_TRAVERSAL,这个类型表示是View绘制即测试、布局、画图。它的回调代码在mTraversalRunnable变量中。

屏障消息就是用来拦截普通消息(或者叫同步消息)的,MessageQueue.next方法中只要遇到屏障方法,同步消息就不会执行,它只执行异步消息,没有异步消息就阻塞一直等。除非移除屏障消息。对这个不清楚的可以再去看看Handler和MessageQueue的源码,这里就不贴了。

mTraversalRunnable这个Runable就是一个绘制任务,即遍历view执行测量、定位、画图。后面再仔细分析。

然后就到了主角:Choreographer

Choreographer刷新UI流程

Choreographer.postCallback最终会调到postCallbackDelayedInternal

//android.view.Choreographer
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
            	//准备绘制
                scheduleFrameLocked(now);
            } else {
            	//这个分支最终也是调用scheduleFrameLocked(now)方法。
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

先将回调类型、代码块、token封装到了CallbackRecord变量中,并插入到队列中:

        public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }

然后调用 scheduleFrameLocked(now)方法准备绘制图像帧。

scheduleFrameLocked方法的实现:

//android.view.Choreographer
    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

其中,USE_VSYNC变量表示是否等待垂直同步信号,否则不需要等待。vsync表示垂直同步信号,就是在显示器刷新两个图像帧的间隙发出的一个信号,告诉GPU要准备下一帧的数据了,应用程序是图像的生产者,显示器就是消费者,显示器的刷新率一般是60HZ,就是1s内刷新60次。应用程序生产数据时两个帧的间隔不应超过16ms,否则就会掉帧(即显示器将旧数据帧重复显示,也就是所谓的卡顿)。

android 4.1开始USE_VSYNC默认是true。

然后会调到scheduleVsyncLocked,表示需要等待一个Vsync信号。

//android.view.Choreographer
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();//等待vsync信号
    }

等待vsync信号和接收vsync信号的对象是mDisplayEventReceiver,它的类型是FrameDisplayEventReceiver,继承自DisplayEventReceiver:

//android.view.Choreographer
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource);
        }

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // Post the vsync event to the Handler.
            // The idea is to prevent incoming vsync events from completely starving
            // the message queue.  If there are no messages in the queue with timestamps
            // earlier than the frame time, then the vsync event will be processed immediately.
            // Otherwise, messages that predate the vsync event will be handled first.
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }

当有vsync信号到来时会回调onVsync方法。发送信号详细流程是在native层实现的,有兴趣翻阅安卓源码。这里就不赘述。

其中onVsync方法会往主线程Looper发一条消息,消息要执行的代码块是当前这个类的run方法。

run方法调用了doFrame方法开始绘制UI。

注意它的第一个参数mTimestampNanos表示的是vsync信号发送的时间,单位是纳秒。

doFrame方法执行时就是UI实际绘制的时间,因为onVsync往主线发的消息虽然是异步的,但也不一定立即执行,可能主线程正在处理耗时的同步任务。等正在处理的任务执行完时就会执行这个异步消息。

利用这个机制,可以在onFrame中统计App实际的帧率。
在onFrame中获取当前时间,然后减去vsync的时间就是绘制一帧的耗时。用一秒除了这个时间就算出来了一秒可以绘制多少帧即帧率。
统计帧率需要往Choreographer中发一条FrameCallback回调:

  Choreographer.getInstance().postFrameCallback(new MyFrameCallback(System.nanoTime()));
  //MyFrameCallback是继承自Choreographer.FrameCallback的自定义类,在它的onFrame方法中计算帧率。

继续看ChoreographerdoFrame方法的源码:

//android.view.Choreographer
  void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }

            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                mDebugPrintNextFrameTimeDelta = false;
                Log.d(TAG, "Frame time delta: "
                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
            }

            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                            + "which is more than the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                if (DEBUG_JANK) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

重点在它的try代码块中,即是执行之前mChoreographerpostCallback方法保存在队列中的任务。

ViewRootImpl绘制UI流程

然后回到ViewRootImpl看看之前postCallback传递的任务是做什么的:

  mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

mTraversalRunnable的类型是:

//android.view.ViewRootImpl
   final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

然后调用了doTraversal方法:

//android.view.ViewRootImpl
   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

然后调用了performTraversals()方法,这个方法就是真正的遍历view,执行测量、布局、画图。

private void performTraversals() {
	//省略无关代码
	performMeasure(...);
	//省略无关代码
	performLayout(...)
	//省略无关代码
	performDraw();
	//省略无关代码
}

到这里整个View绘制就形成了闭环。

整体流程图

用流程图总结一下整体流程:

ActivityThread.handleResumeActivity
Activity.onResume
WindowManager.addView--将DecorView添加到窗口中
ViewRootImpl.setView
ViewRootImpl.requestLayout
ViewRootImpl.scheduleTraversals--准备遍历绘制UI
Choreographer.postCallback--将遍历任务存入队列稍候执行
Choreographer.scheduleVsyncLocked--等待vsync信号
FrameDisplayEventReceiver.onVsync--往主线程发送异步消息
Choreographer.doFrame--主线程开始绘制UI
ViewRootImpl.doTraversal
ViewRootImpl.performTraversals
performMeasure--遍历子view开始测量
performLayout--遍历子view开始定位
performDraw--遍历子view开始画图

View和Surface的关系

未完待续…

View和SurfaceFlinger、OpenGL ES的关系

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值