15分钟带你彻底了解App绘制流程-安卓篇

发布时间:2023年12月20日

前言

在了解绘制流程,首先我们需要知道什么是Vsync,没有了解的同学先去看看我的这篇文章,看了后必定如雷灌顶。
2分钟带你了解什么是Vsync
回顾下这篇的内容,当程序在屏幕绘制一个画面的时候,他需要经过这样的流程:程序业务代码,经过cpu预算,计算所需要的数据,运算到gpu合成完,发送到FrameBuffer,然后被显示器读取,显示到屏幕。
在这里插入图片描述
有了这些概念后,我们开始探入安卓绘制流程。

安卓绘制流程

先说下思路,后面再一步步验证。我们已经知道,每当设备绘制完一帧后就会发出一个Vsync信号,而安卓的绘制流程第一步就是订阅这个信号,当需要屏幕刷新的时候(也就是你调用invalidate或requestLayout),系统就会就加入一个回调,当这个Vsync来到时候,系统就会执行这个回调,这个回调里面会将说有的视图进行业务的布局构建,然后将数据发送到gpu汇总成一个画面数据发送到缓冲区,再给显示器绘制显示出来。我们可以先粗略看下面这个图
在这里插入图片描述

了解视图链路

到了这里,我们已经了解大概流程,我们从视图链路层出发,认知整条连的关系,再从代码中认证。
在这里插入图片描述
ViewGroup和View的关系这里就不探讨了,相信大家已经清楚。从这个图我们可以发现,所有部件的顶端是一个DecorView,他的mParent和所有的不同,却是一个ViewRootImpl,并且所有子节点都可以间接访问到它。这个东西非常重要,它的作用是触发安排下一次的Vsync,然后执行回调将所有需要绘制的视图进行构建渲染!而且ViewRootImpl是每个窗口唯一,下面即将通过源码来验证这条链的关系。

我们先找到ActivityThread.java, 来到handleResumeActivity方法,这个方法主要是在Activity执行onCreate回调后,再执行onResume之前执行下面代码.这里不讨论其他的,粗略看主要代码即可,不想要看详细实现,不然我们根本看不完,看流程即可。

    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
      //...省略
	 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) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } 
            }
            //...

	}

}

找到,并点进去

   wm.addView(decor, l);

点进去可以发现,它是个接口,是由WindowManager继承的ViewManager的接口,他的实现类是WindowManagerImpl
在这里插入图片描述

因此我们去找他的实现方法addView

   //....
    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());
    }


可见,addView并不是直接实现,由WindowManagerGlobal mGlobal代理实现,我们继续点进去看看。

   public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
            //...
            ViewRootImpl root;
            // ....
            if (windowlessSession == null) {
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }
            //...
            mViews.add(view);//将DecorView加到窗口mViews里面
            mRoots.add(root);//将ViewRootImpl加到窗口的mRoots
            //...
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
               //....
            }
}


由此可见,ViewRootImpl 是在Acitivity的onResume 时候创建的
下一步,找到

	 root.setView(view, wparams, panelParentView, userId);

进去,发现他调用了requestLayout(),上面已经说过,调用这个方法会安排一个回调,下次Vsync信号回来就执行所有需要的绘制画面。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
 //...
      mView = view; //将DecorView保存到mView
 //...
      requestLayout();
  //...
   view.assignParent(this);

}

并将ViewRootImpl设置到DecorView,作为DecorView的mParent

 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");
        }
  }

由于一个Activity只有一个Window,也就是PhoneWindow,这个PhoneWindow并不是真正意义的视图窗口,他是由DecorView来提供视图,是本窗口所有视图的最底层视图.

在ViewGroup里面,他会调用子view的assignParent,将子view的mParent指向它。我们可以看看当我们添加View时候的方法。

   public void addView(View child) {
        addView(child, -1);
    }
    public void addView(View child, int index) {
        //....省略
        addView(child, index, params);
    }
public void addView(View child, int index, LayoutParams params) {
    ///....省略
    addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
     //....省略   
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
}

再回顾下上面的图,就很清晰链路上的关系。子节点有了ViewParent就可以形成一条链,需要进行刷新的时候就可以将整条链进行遍历作业,最终获取到ViewRoorImpl去 schedule一个Vsync,等待订阅的Vsync过来,然后进行构建显示到屏幕

了解Vsync来时,执行的过程

我们先快速浏览下如图相关的类,留个初步印象,方便解读

相关类图

1.与视图生命周期相关
在这里插入图片描述
2. 与Vysnc响应与执行相关
在这里插入图片描述

流程概况

先简单说下总体流程,再通过源码来验证。完成这个个关键的类主要是3个, Choreographer.java,DisplayEventReceiver.java,ViewRootImpl.java. 通过阅读我们已经了解ViewRootImpl.java与视图的关系。**ViewRootImpl.java在整个环节中,主要负责管理视图的布局与绘制业务。**其中,ViewRootImpl与Choreographer各自持有双方的对象,如图
在这里插入图片描述
Choreographer的作用主要是管理Vysnc的安排与接收。Choreographer只是起到一个中介的作用,真正实现是他拥有的DisplayEventReceiver这个广播。
在这里插入图片描述

从源码角度解锁流程

第一步:初始化以及绑定Vsync消息中心

在这里插入图片描述

第二步: 当View绘制的时候,请求安排下一个Vsync进行处理

在这里插入图片描述

第三步:执行Vsync回来后的绘制工作

在这里插入图片描述

未编辑完,待更新

文章来源:https://blog.csdn.net/YoYo_Newbie/article/details/134961968
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。