Android的WMS讲解

WindowManagerService简称WMS,同前面讲解的PMS、AMS一样都是由system_server进程启动的系统的核心服务,先看下WMS的概念以及作用。

1.概念及作用

负责管理和控制所有应用程序窗口的创建、显示、更新和销毁。它充当着Android窗口系统的守护者,确保窗口的正确显示和交互。它的作用如下(以下由ChatGPT回答):

  • 窗口管理:WMS负责跟踪和管理所有应用程序窗口,包括活动(Activity)窗口、对话框、通知栏、悬浮窗等。它确保窗口的层级关系、位置、尺寸和显示状态的正确性。
  • 用户交互:WMS处理用户与窗口之间的交互,例如点击、滑动和手势操作等。它将用户的输入事件分发给对应的窗口,以便应用程序可以响应用户的操作。
  • 窗口动画:WMS支持窗口的动画效果,如打开、关闭和切换窗口时的过渡动画。这些动画可以提升用户界面的吸引力和流畅性。
  • 窗口策略:WMS定义了一系列窗口策略,例如窗口焦点管理、多窗口模式、屏幕旋转等。它确保窗口的行为符合Android系统的规范和用户的期望。

接下来我会先从以下几点来介绍:

(1)先从WMS服务的启动开始讲起。

(2)接着带着大家了解下AMS、PMS和WMS的关系。

(3)然后我们再一起看下app加载的全过程,它是如何从zygote进程孵化后一步步的执行到Application的onCreate以及Activity的onCreate方法。

(4)最后我们再看下setContentView里面做了什么,数据是如何从xml文件到绘制出来变成我们看到的图像。Surfacefilnger是做什么的?ViewRootImpl是做什么的?Choreographer又是做什么的?

本篇文章的核心就是一起来探究下UI是如何进行具体绘制的。

2.源码讲解

首先我们应该都知道WMS是由system_server来创建了,这个已经是老生常谈的了,看下代码:

public final class SystemServer {

    WindowManagerService wm = null;

    private void startOtherServices() {
        wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                    new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
    }
}

在上一篇介绍AMS的文章中,我们主要分析的是AMS是如何跟PMS产生联系的,然后从startActivity作为入口开始分析AMS加载Activity的过程以及如何管理生命周期的。

至此,我们已经知道了Activity是怎么创建出来的了,回顾下我们在讲解AMS的时候,提到Activity的生命周期,显示执行LaunchActivityItem,然后执行到ActivityThread中的performLaunchActivity()方法,该方法内部调用Instrumentation的newActivity方法通过反射来创建Activity。

Activity创建好了,那接下来我们进入onCreate()内部,还记得我们怎么在Activity中绑定布局的吗?setContentView(R.layout.xxx);很简单的,对不对。我们以这个方法为入口开始往下讲解。

public class Activity {

    private Window mWindow;

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    public Window getWindow() {
        return mWindow;
    }

    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,
            IBinder shareableActivityToken) {
        attachBaseContext(context);

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

        mWindow = new PhoneWindow(this, window, activityConfigCallback);

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        mWindowManager = mWindow.getWindowManager();
    }
}

Window类是个抽象类,那mWindow是如何被初始化的呢?

我们回过头看下ActivityThread中的performLaunchActivity方法,这个方法我们上面提到过,onCreate生命周期就是这个方法触发的,我们进去看看。

activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken, r.shareableActivityToken);

有一句关键语句,回过头看上面Activity中的attach方法。发现mWindow实际上是PhoneWindow类。所以setContentView实际上是调用的PhoneWindow的setContentView方法。

public class PhoneWindow {
    
    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

    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) {
        ...
        //DecorView是一个FragmentLayout
        return new DecorView(context, featureId, this, getAttributes());
    }

    protected ViewGroup generateLayout(DecorView decor) {
        ...
        int layoutResource;
        int features = getLocalFeatures();
        //随便挑了一个if判断
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if (xxx) {

        } else if (xxx) {

        }
        ......

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        return contentParent;
    }

}
public class DecorView extends FrameLayout {
    
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...
        final View root = inflater.inflate(layoutResource, null);

        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
}

先看PhoneWindow中的installDecor()方法,该方法内部实现先通过generateDecor()创建了mDecor,接着通过generateLayout()创建mContentParent。重点看下generateLayout()方法,根据我们设置的feature来选择系统内置的布局文件,比如上面的screen_title_icons.xml文件,接着解析得到View添加到mDecor中。返回的contentParent实际上是布局中定义的一个子节点。系统内置的布局文件一般结构如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    ......省略......

    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

至此我们再看下下面这幅图我们应该就不陌生了。

view层级结构

上图是通过AndroidStudio自带的Layout Inspector查看的页面布局,最外层是DecorView,里面一层是根据不同的feature选择的不同的样式层,最里面一层才是我们自己写的。

回到上面的PhoneWindowsetContentView()方法,我们刚刚走完installDecor(),接着看mLayoutInflater.inflate(layoutResID, mContentParent),这句话就是开始解析我们自己写的布局了。

public abstract class LayoutInflater {

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        ...
    }

    //最终会走到这里
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

        ...
        View result = root;
        ...
        // Temp is the root view that was found in the xml
        //最终通过反射创建view
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);

        rInflateChildren(parser, temp, attrs, true);

        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }
        if (root == null || !attachToRoot) {
            result = temp;
        }
        ...
        return result;
    }

    final void rInflateChildren(xxx) {
        rInflate(xxx);
    }

    //rInflateChildren()最终会调用到这里
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            //我们一般自定义view都会在这个回调里面处理findViewById和其他业务逻辑等
            parent.onFinishInflate();
        }
    }
}

重点就是rInflate()方法,递归解析我们写的xml文件,所以这也就是为什么在性能优化的时候都有特别说明说布局层级不要太深,原因就在这里,因为布局深了就要递归很深,而递归不会释放栈的,随意嵌套层级深的布局不仅浪费解析时间,同时也会浪费系统的内存资源。

最终我们自己定义的布局完全解析出来后加到了root.addView(temp, params),root也就是mContentParent。

那布局都解析完了,数据都准备好了,那准备好的这些View啊啥的给谁处理呢?或者换个问法,这些准备好的View是如何画出来的呢?

上面只分析了onCreate,那onCreate执行完了,接下来就是onResume了。

结合上一篇博客讲解的AMS相关知识点,我们知道接下来执行的是ResumeActivityItem类的execute方法,最终会走到ActivityThread中的handleResumeActivity方法。

public class ActivityThread {
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest, boolean isForward, String reason) {

        ...
        final Activity a = r.activity;
        ...
        ViewManager wm = a.getWindowManager();
        ...
        wm.addView(decor, l);
    }
}

最后一句我们先不看,先看ViewManager是什么,它是个接口,那实现类是谁呢?a.getWindowManager()返回的是WindowManager mWindowManager。我们再回到上面看下Activity的attach方法,通过mWindow.setWindowManager方法给mWindowManager赋值,setWindowManager方法是在PhoneWindow的子类Window类中定义的,最终会发现下面这句话:mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this),所以我们知道了mWindowManager是一个WindowManagerImpl对象。

接着再看addView,那很显然调用的是WindowManagerImpl中的addView方法。

public final class WindowManagerImpl implements WindowManager {

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

}
public class WindowManagerGlobal {
    public void addView(...) {
        ...
        ViewRootImpl root;
        root = new ViewRootImpl(view.getContext(), display);
        ...
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        ...
        root.setView(view, wparams, panelParentView, userId);
    }
}
public class ViewRootImpl {

    final Choreographer mChoreographer;

    public void setView(View 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();
        ...
        view.assignParent(this);
        ...
    }

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

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

ViewRootImpl管理所有View的绘制策略,该怎么画,这个类说的算!另外view.assignParent(this)这句话,让View反向持有ViewRootImpl对象。还有句关键的地方就是requestLayout(),这里最终会调用mChoreographer.postCallbackmChoreographer也就是编舞者。这个知识点下一小节再说。

接下来我们就来看看图像具体是如何绘制的。

3.图像绘制基础知识

本小结的重点是View在绘制之前是如何管理的,以及如何绘制出图像。

在讲绘制之前我们需要了解下刷新率帧速率

刷新率(屏幕):表示在1s内刷新屏幕的次数,这个值是硬件固定参数。比如60HZ,表示每秒刷新60次,也就是16.66ms刷新一次。

帧速率(GPU):表示GPU在1s内绘制的帧数。比如60FPS表示在1s内GPU能制作60张图。

那这里就有个问题了,因为刷新率和帧速率分别由屏幕和GPU两个硬件来控制,当刷新率和帧速率不一致时就会出现问题。我们先看正常的情况。

刷新率和帧速率一致

GPU制作的图像数据存储到一个叫帧缓冲区的内存中,我们可以将帧缓冲理解为一个M*N矩阵,数据从上到下一行一行保存,显示器在显示的时候,从上到下逐行扫描,依次显示在屏幕上,我们把这样的一屏数据叫做一帧,当一帧数据渲染完后,就开始新一轮扫描,如果CPU正好也把下一帧数据写入帧缓冲,那么就会显示下一帧画面,如此循环,我们就看到了不断变化的画面,也就是图像。但是帧率和刷新率有不一致的情况,比如下面。

再看帧率高于刷新率的情况。

帧率高于刷新率

帧率高于刷新率时,也就是GPU生产的速度很快,而屏幕展示的速度很慢,会导致屏幕展示不及时,从而丢失掉很多GPU制作的数据,从而造成卡顿现象。

再看刷新率高于帧率的情况。

刷新率大于帧率

刷新率高于帧率,也就是GPU生产的速度跟不上屏幕展示的速度,导致屏幕在某一帧展示的时候可能无数据展示,从而会出现白屏黑屏等闪屏现象。

用过安卓早期的手机比如4.0系统之前的手机都有个深刻的体验就是卡。在4.0之后为了解决UI卡顿的问题,使用Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。其中,VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种在PC上已经很早就广泛使用的技术。可简单的把它认为是一种定时中断。

关于Vsync的原理,可以看看这篇文章https://zhuanlan.zhihu.com/p/420329041,写的非常好,我这里就不班门弄斧了。另外还有这一篇https://mp.weixin.qq.com/s/XUzrxcz9E3P-Yl1YWW_urw,写的也很好。我把几个关键的信息贴过来,大家也可以直接跳过去查看。

上面说到刷新率和帧速率不一致会导致画面撕裂,那是因为早期的4.0之前设备是只有一个buffer,且其并没有buffer同步的概念,屏幕读取buffer中的数据时,GPU是不知道的,屏幕读取的同时,GPU也在写入,导致buffer被覆盖,出现同一画面使用的是不同帧的数据。

那既然是因为使用同一个Buffer引起的画面撕裂,使用两个buffer不就可以了?

双缓冲
前面我们说到画面撕裂是由于单buffer引起的,在4.1之前,使用了双缓冲来解决画面撕裂。

  • GPU写入的缓存为:Back Buffer
  • 屏幕刷新使用的缓存为:Frame Buffer

因为使用双buffer,屏幕刷新时,frame buffer不会发生变化,通过交换buffer来实现帧数据切换,那什么时候交换buffer呢?

这就要引入Vsync的概念了。

VSync(垂直同步)

我们知道如果一个屏幕在刷新的过程中,是不能交换buffer的,只有等屏幕刷新完成后以后才可以考虑buffer的交换.

那具体什么时候交换呢?当设备屏幕刷新完毕后到下一帧刷新前,因为没有屏幕刷新,所以这段时间就是缓存交换的最佳时间。

此时硬件屏幕会发出一个脉冲信号,告知GPU和CPU可以交换了,这个就是Vsync信号。

有了双缓冲和VSync是不是就都ok了?虽然上面方式可以解决屏幕撕裂的问题,但是还是会出现一些其他问题。

Jank

双缓冲buffer交换还有个前提就是GPU已经准备好了back buffer的数据,如果在Vsync到来时back buffer并没有准备好,就不会进行缓存的交换,屏幕显示的还是前一帧画面,这种情况就是Jank。

有了上面的基础我们再来聊聊Android屏幕刷新机制的演变过程

演变过程

Android屏幕刷新机制演变过程按buffer的个数可以分为3个阶段:

  1. 单buffer时代
  2. 双buffer时代
  3. 三buffer时代

单buffer时代

GPU和显示器共用一块buffer,会引起画面撕裂。

双buffer时代

在引入VSync前(Drawing without VSync)

  • CPU:表示CPU绘制的时间段
  • GPU:表示GPU合成back buffer的时间段
  • Display:显示器读取frame buffer的时间段

按时间顺序:

  1. Display显示第0帧画面,而CPU和GPU正在合成第1帧,且在Display显示下一帧之前完成了。
  2. 由于GPU在Display第一个VSync来之前完成了back buffer的填充,此时交换back buffer和frame buffer,屏幕进行刷新,可以正常显示第一帧数据。
  3. 再来看第2个VSync,第二个VSync到来之时,GPU并没有及时的填充back buffer,这个时候不能交互buffer,屏幕刷新的还是第1帧的画面。就说这里发生了“jank”
  4. 在第3个VSync信号到来时,第2帧的数据已经写入back buffer,第3帧的数据GPU还没写入,所以这个时候交互buffer显示的是第2帧的数据
  5. 同理,在第4个VSync时,第3帧数据已经处理完毕,交换buffer后显示的是第2帧的数据

这里发生jank的原因是:在第2帧CPU处理数据的时候太晚了,GPU没有及时将数据写入到buffer中,导致jank的发生。

如果可以把CPU绘制流程提前到每个VSync信号来的时候进行CPU的绘制,那是不是就可以让CPU的计算以及GPU的合成写入buffer的操作有完整的16.6ms。

在引入VSync后(Drawing with VSync)

为了进一步优化性能,谷歌在4.1之后对屏幕绘制与刷新过程引入了Project Butter(黄油工程),系统在收到VSync信号之后,马上进行CPU的绘制以及GPU的buffer写入。这样就可以让cpu和gpu有个完整的16.6ms处理过程。最大限度的减少jank的发生。

引入VSync后,新的问题又出现了:如下图:

由于主线程做了一些相对复杂耗时逻辑,导致CPU和GPU的处理时间超过16.6ms,由于此时back buffer写入的是B帧数据,在交换buffer前不能被覆盖,而frame buffer被Display用来做刷新用,所以在B帧写入back buffer完成到下一个VSync信号到来之前两个buffer都被占用了,CPU无法继续绘制,这段时间就会被空着, 于是又出现了三缓存。

三buffer时代

为了进一步优化用户体验,Google在双buffer的基础上又增加了第三个buffer(Graphic Buffer), 如图:

按时间顺序:

  1. 第一个jank是无法避免的,因为第一个B帧处理超时,A帧肯定是会重复的。
  2. 在第一个VSync信号来时,虽然back buffer以及frame buffer都被占用了,CPU此时会启用第三个Graphic Buffer,避免了CPU的空闲状态。

这里可以最大限度避免2中CPU空闲的情况,记住只是最大限度,没有说一定能避免。

那又有人要说了,那就再多开几个不就可以了,是的,buffer越多jank越少,但是你得考虑性价比:3 buffer已经可以最大限度的避免jank的发生了,再多的buffer起到的作用就微乎其微,反而因为buffer的数量太多,浪费更多内存,得不偿失。

不过假想下哪天由于硬件的改进,3 buffer已经满足不了的时候,谷歌又会加4 buffer,5 buffer..这都是可能的事情。

以上我觉得作者写的非常好,通俗易懂,所以摘抄过来。

介绍完了刷新率、帧率,以及Android刷新机制的演变过程,我们再接着看源码。

4.图像是如何绘制的

上面说的编舞者Choreographer,它的作用是配合Vsync,给上层App的渲染提供一个稳定的绘制处理时机,也就是Vsync的到来的时候,Choreographer可以接收Vsync信号,统一管理应用的输入、动画、绘制等任务的执行。Android的UI绘制任务将在它的统一指挥下完成。

ViewRootImpl调用setView,再调用requestLayout,接着调用scheduleTraversals,最后发起mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 接着看编舞者源码。

public final class Choreographer {
    
    //帧率
    private long mFrameIntervalNanos;
    private long mLastFrameTimeNanos;
    private final FrameDisplayEventReceiver mDisplayEventReceiver;

    private Choreographer(Looper looper, int vsyncSource) {
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        mLastFrameTimeNanos = Long.MIN_VALUE;
    }

    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) {
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
        ...
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        ...

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

    void doScheduleCallback(int callbackType) {
        synchronized (mLock) {
            if (!mFrameScheduled) {
                final long now = SystemClock.uptimeMillis();
                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                    scheduleFrameLocked(now);
                }
            }
        }
    }

    private void scheduleVsyncLocked() {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
            //这里会进入native层
            mDisplayEventReceiver.scheduleVsync();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
        // 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,
                VsyncEventData vsyncEventData) {
            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, mLastVsyncEventData);
        }
    }


    void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) {

        ...

        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        //当前时间减去vsync信号来的时间
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= frameIntervalNanos) {//整体时间大于一帧时间
            //下面的逻辑就是计算到底跳了多少帧。
            //一帧是16.66ms,计算当前跳了多少帧,比如超时166.6ms,就是跳了10帧
            final long skippedFrames = jitterNanos / frameIntervalNanos;
            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 % frameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                        + "which is more than the frame interval of "
                        + (frameIntervalNanos * 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.");
            }
            traceMessage("Frame time goes backward");
            scheduleVsyncLocked();
            return;
        }

        if (mFPSDivisor > 1) {
            long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
            if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                traceMessage("Frame skipped due to FPSDivisor");
                scheduleVsyncLocked();
                return;
            }
        }

        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
        mLastFrameIntervalNanos = frameIntervalNanos;
        mLastVsyncEventData = vsyncEventData;

        ...

        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

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

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

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);

        ...
    }

    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;

        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }

        for (CallbackRecord c = callbacks; c != null; c = c.next) {

            c.run(frameTimeNanos);
        }
    }

}

类很长,只放了些关键代码,postCallback后最终会走到mDisplayEventReceiver.scheduleVsync(); 这个方法走进去后最终会进入到native层,然后native层会回调到FrameDisplayEventReceiver类的onVsync方法,接着执行到doFrame方法。这个方法内部主要是根据设定的帧率mFrameIntervalNanos,以及最后一帧的时间mLastFrameTimeNanos来决定vsync信号发送的时机。

接着再往下看,执行了很多个doCallbacks,我们重点看callbackType类型是Choreographer.CALLBACK_TRAVERSAL,因为在ViewRootImpl的scheduleTraversals中调用的就是这个类型。最终会走到ViewRootImpl的performTraversals方法,在上面ViewRootImpl源码的基础上来把它补充完整。

public class ViewRootImpl {

    final Choreographer mChoreographer;

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    // These can be accessed by any thread, must be protected with a lock.
    // Surface can never be reassigned or cleared (use Surface.clear()).
    public final Surface mSurface = new Surface();
    private final SurfaceControl mSurfaceControl = new SurfaceControl();

    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }

    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
        this(context, display, session, false /* useSfChoreographer */);
    }

    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        ...
        mWindowSession = session;
        ...
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        performTraversals();
    }

    public void setView(View 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();
        ...
        view.assignParent(this);
        ...
    }

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

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

    private void performTraversals() {
        relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

        performDraw()--->draw()---->drawSoftware()
    }

    private int relayoutWindow(...) {
        ...
        int relayoutResult = mWindowSession.relayout(...);
        //创建native层的surface
        mSurface.copyFrom(mSurfaceControl);
        ...
    }


    private boolean drawSoftware(...) {
        // Draw with software renderer.
        final Canvas canvas;

        canvas = mSurface.lockCanvas(dirty);

        mView.draw(canvas);

        //发送绘制请求 主要作用就是通知Surfaceflinger从BufferQueue中取出数据合成Surface并发送到FrameBuffer中。
        surface.unlockCanvasAndPost(canvas);
    }
}

其中比较重要的是relayoutWindow方法以及draw方法。

先看relayoutWindow方法。

这个方法的调用过程就不贴完整的源码了,感兴趣自己去查阅下。看下下面的调用流程图。

relayoutWindow过程

ViewRootImpl调用relayoutWindow,通过Session来调用WMS,这里的Session我们简单看下如何构造的,对应的是ViewRootImpl中的mWindowSession变量,这个值是通过WindowManagerGlobal.getWindowSession()来构造,进入WindowManagerGlobal中的getWindowSession()方法发现最终是通过WMS的openSession方法来返回的。

//WindowManagerGlobal.java
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
    new IWindowSessionCallback.Stub() {
        @Override
        public void onAnimatorScaleChanged(float scale) {
            ValueAnimator.setDurationScale(scale);
        }
    });
//WindowManagerService.java
public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }

Session继承了IWindowSession.Stub,它实际上是一个Binder,用来做跨进程通信的。所以Session我们可以理解为WMS跟外界沟通的桥梁。

接着WMS的relayoutWindow往下看,这个方法主要的作用就是计算窗体相关信息,然后构造一个native层的Surface。最终这个Surface作为底层Surfaceflinger的数据源。看下Surfaceflinger作用。

接着看draw方法,最终走到drawSoft方法,这个方法里面通过surface来构造一个Canvas,然后Canvas就开始生成图像数据——也就是bitmap了。这里面有个重要的方法unlockCanvasAndPost,调用流程如下。

通过unlockCanvasAndPost来通知SF来从帧缓冲区BufferQueue中取数据来合成一个数据buffer来传递给图像引擎如OpenGL来处理,最终交给显示器。

SurfaceFlinger作用是接受多个来源的图形显示数据Surface,合成后发送到显示设备。

比如我们的主界面中:可能会有statusBar,侧滑菜单,主界面,这些View都是独立Surface渲染和更新,最后提交给SF后,SF根据Zorder,透明度,大小,位置等参数,合成为一个数据buffer,传递HWComposer或者OpenGL处理,最终给显示器。

surfaceflinger工作原理

至此,整个流程都介绍完了。从Activity创建,到解析xml,生成View实例,再到编舞者和ViewRootImpl来控制绘制,再到生成Surface,再到Canvas生成Bitmap数据,再到数据交给BufferQueue,然后通知SF来取BufferQueue中的数据,然后合成数据buffer最终显示出来。