Android UI绘制流程前奏

Android UI绘制流程前奏,第1张

概述1.前言在android当中对于UI体系当中往往我们会在绘制UI的时候碰到各种各样的问题而不知道从何解决,也有时需要开发更改自定义组件时,需要做自己的调整,或者是实现某个自定义特效时的思路不明确,因此了解UI绘制流程及原理是十分必要的,本文就UI绘制流程之前的相关知识进行简单的 1.前言

在androID当中对于UI体系当中往往我们会在绘制UI的时候碰到各种各样的问题而不知道从何解决, 也有时需要开发更改自定义组件时,需要做自己的调整,或者是实现某个自定义特效时的思路不明确,因此了解UI绘制流程及原理是十分必要的,本文就UI绘制流程之前的相关知识进行简单的分析和梳理,便于后续进一步了解UI绘制原理

2.VIEw是如何添加到屏幕窗口上的

要弄清楚UI绘制流程和原理,我们首先要了解的就是VIEw是如何被添加到屏幕窗口上的。带着这个问题我们来进行源码分析,关于界面的展示,立马浮现在脑海的就是这样一段代码:

public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_main);    }}

通过传入布局资源ID,setContentVIEw方法又做了什么事情呢?经过一系列线索最终找到了的位置也就是Window的唯一实现类PhoneWindow:

public class PhoneWindow extends Window implements MenuBuilder.Callback {    ...    // This is the top-level vIEw of the window, containing the window decor.    // 这是在窗口当中的顶层VIEw,包含窗口的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.    // 这是窗口内容放置的视图,它要么是mDecor本身,要么是mDecor的子类的内容    VIEwGroup mContentParent;    ...    @OverrIDe	public voID setContentVIEw(int layoutResID) {		        if (mContentParent == null) {            // 注释1            installDecor();        }         ...            // 注释2            mLayoutInflater.inflate(layoutResID, mContentParent);                ...	}}

注释1处installDecor方法顾名思义就是初始化 *** 作,注释2处就是将布局资源ID填充到mContentParent内容布局容器。先进入installDecor方法:

private voID installDecor() {    mForceDecorInstall = false;    if (mDecor == null) {        // 注释1        mDecor = generateDecor(-1);        ...    }    if (mContentParent == null) {        // 注释2        mContentParent = generateLayout(mDecor);		...    }}

注释1处,如果mDecor为空,就调用generateDecor方法,进入该方法就发现通过返回一个new出来DecorVIEw,然后赋值给mDecor。注释2处调用generateLayout方法,那么该方法是如何给mContentParent赋值的呢?

protected VIEwGroup generateLayout(DecorVIEw decor) {    ...    // 注释1    // 根据系统主题的属性设置了许多了特性    if (a.getBoolean(R.styleable.Window_windowActionbarOverlay, false)) {        requestFeature(FEATURE_ACTION_bar_OVERLAY);    }    if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {        requestFeature(FEATURE_ACTION_MODE_OVERLAY);    }    if (a.getBoolean(R.styleable.Window_windowswipetodismiss, false)) {        requestFeature(FEATURE_SWIPE_TO_disMISS);    }    if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {        setFlags(FLAG_FulLSCREEN, FLAG_FulLSCREEN & (~getForceDWindowFlags()));    }    ...    // 注释2    // Inflate the window decor.    int layoutResource;// 布局资源ID    int features = getLocalFeatures();        if ((features & (1 << FEATURE_SWIPE_TO_disMISS)) != 0) {        // 注释3         // 根据不同feature, 对layoutResource进行不同的赋值 *** 作        // 即后续加载不同的布局,这就很好的解释了为什么我们自己要去getwindow.requestFeature时        // 必须在setContent之前的原因        layoutResource = R.layout.screen_swipe_dismiss;        setCloSEOnSwipeEnabled(true);    } else if ((features & ((1 << FEATURE_left_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {        ...    }    mDecor.startChanging();    // 注释4    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);	// 注释5    // ID_ANDROID_CONTENT = com.androID.internal.R.ID.content;    VIEwGroup contentParent = (VIEwGroup)findVIEwByID(ID_ANDROID_CONTENT);    if (contentParent == null) {        throw new RuntimeException("Window Couldn't find content container vIEw");    }    ...    return contentParent;}

省去了很多类似的特性设置代码,在注释1处我们发现根据系统属性的不同,通过requestFeature和setFlag方法设置了许多属性。在注释2处,看到解析窗口decor的提示,继续往下看,如注释3处,会根据不同的特性对布局资源进行不同的赋值,即后续加载不同的布局(就是不同的Actionbar,Titlebar之类的)。这就是为什么我们自己要去getwindow.requestFeature时必须在 setContent之前的原因。再看注释4处的onResourcesLoaded方法:

voID onResourcesLoaded(LayoutInflater inflater, int layoutResource) {    ...        addVIEw(root, 0, new VIEwGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    ...    mContentRoot = (VIEwGroup) root;    initializeElevation();}

主要逻辑就是将传入layoutResource即布局资源通过addVIEw添加到DecorVIEw中。在回到注释5处,通过findVIEwByID,获取ID为com.androID.internal.R.ID.content的contentVIEw,即内容布局容器,最后返回。分析完installDecor方法,再回到PhoneWindow的setContentVIEw方法的注释2处,调用inflate方法,就是将MainActivity的layoutResID即对应的资源布局,添加到mContentParent内容布局容器。至此setContentVIEw的分析就告一段落。

方法内部逻辑比较多,主要做了以下几件事:

installDecor方法内部的generateDecor方法初始化DecorVIEw

installDecor方法内部的generateLayout

根据不同的系统属性,通过requestFeature和setFlag方法设置不同(feature)特性

根据不同的feature,通过onResourcesLoaded方法的addVIEw加载不同的layoutResource(布局资源,一般是Actionbar,Title等)

通过findVIEwByID获取固定ID为com.androID.internal.R.ID.content的内容布局容器contentParent

返回contentParent

setContentVIEw方法内通过inflate方法将初始的layoutResID对于的布局添加到contentParent布局容器(androID.R.ID.content)

总结一下,VIEw是如何添加到屏幕窗口上的,主要分为三个步骤:

创建顶层布局容器DecorVIEw在顶层布局中加载基础布局容器VIEwGroup将ContentVIEw添加到基础布局中的FrameLayout中3.VIEw的绘制流程3.1绘制入口

谈到VIEw的绘制入口,就需要知晓Activity的启动过程,如果还不太清楚可以查阅下面两篇文章了解相关细节
Activity的启动流程分析与总结
Application创建流程分析
受篇幅所限,就不具体分析了。就Activity启动过程的部分与VIEw绘制相关的流程进行简单的梳理,如下图

在handleLaunchActivity方法中调用performlaunchActivity后续会调用Activity的onCreate方法,在performlaunch之后会调用handleResumeActivity方法,顾名思义就知道它会是onResume方法的入口,走进该方法:

final voID handleResumeActivity(IBinder token,        boolean clearHIDe, boolean isForward, boolean reallyResume, int seq, String reason) {    ActivityClIEntRecord r = mActivitIEs.get(token);    ...    // 注释1    // 回调Activity的生命周期方法onResume    r = performResumeActivity(token, clearHIDe, reason);    if (r != null) {        final Activity a = r.activity;        boolean willBeVisible = !a.mStartedActivity;        if (!willBeVisible) {            try {                willBeVisible = ActivityManager.getService().willActivityBeVisible(                        a.getActivityToken());            } catch (remoteexception e) {                throw e.rethrowFromSystemServer();            }        }                if (r.window == null && !a.mFinished && willBeVisible) {            r.window = r.activity.getwindow();            // 注释2            VIEw decor = r.window.getDecorVIEw();            decor.setVisibility(VIEw.INVISIBLE);            // 注释3            // 调用Activity的getwindowManager获取wm            VIEwManager wm = a.getwindowManager();            // 注释4            // 获取窗口的布局属性对象            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;                VIEwRootImpl impl = decor.getVIEwRootImpl();                if (impl != null) {                    impl.notifyChildRebuilt();                }            }            if (a.mVisibleFromClIEnt) {                if (!a.mWindowAdded) {                    a.mWindowAdded = true;                    // 注释5                    wm.addVIEw(decor, l);                } else {                    a.onWindowAttributesChanged(l);                }            }            ...}

在注释2处调用window的getDecorVIEw方法,最终还是调用PhoneWindow的相关方法获取DecorVIEw,在注释3处调用Activity的getwindowManager方法获取VIEwManager,在注释4处获取窗口的布局属性对象,在注释5处调用WindowManager的addVIEw方法,进入Activity的getwindowmanger方法:

public WindowManager getwindowManager() {    return mWindowManager;}

在Activity中搜索mWindowManager赋值的逻辑:

final voID attach(Context context, ActivityThread aThread,    ...    mWindowManager = mWindow.getwindowManager();    ...}

接着进入Window中查找mWindowManager赋值的地方

public voID setwindowManager(WindowManager wm, IBinder appToken, String appname,        boolean harDWareAccelerated) {    ...    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}

接着进入createLocalWindowManager方法,来到了WindowManagerImpl 即WindowManager的实现类:

public WindowManagerImpl createLocalWindowManager(Window parentwindow) {    return new WindowManagerImpl(mContext, parentwindow);}

进入WindowManagerImpl的addVIEw方法:

@OverrIDepublic voID addVIEw(@NonNull VIEw vIEw, @NonNull VIEwGroup.LayoutParams params) {    applyDefaultToken(params);    mGlobal.addVIEw(vIEw, params, mContext.getdisplay(), mParentwindow);}

接着进入mGlobal即WindowManagerGlobal的addVIEw方法:

public voID addVIEw(VIEw vIEw, VIEwGroup.LayoutParams params,        display display, Window parentwindow) {    ...	    VIEwRootImpl root;    ...		// 注释1        root = new VIEwRootImpl(vIEw.getContext(), display);		// 注释2        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 {            // 注释3            root.setVIEw(vIEw, wparams, panelParentVIEw);        } catch (RuntimeException e) {            ...            throw e;        }    }}

在注释1处,实例化了一个VIEwRootImpl,在注释2处,设置布局参数,添加到相关集合,在注释3处通过VIEwRootImpl的setVIEw方法将VIEw和布局参数等进行了关联,进入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.	requestLayout();	...}

需要关心的代码就这一句requestLayout,我们知道该方法会触发VIEw的绘制流程,进入该方法:

@OverrIDepublic voID requestLayout() {    if (!mHandlingLayoutInLayoutRequest) {        checkThread();        mLayoutRequested = true;        scheduleTraversals();    }}

进入scheduleTravels方法:

@UnsupportedAppUsagevoID scheduleTraversals() {    if (!mTraversalScheduled) {        mTraversalScheduled = true;        mTraversalbarrIEr = mHandler.getLooper().getQueue().postsyncbarrIEr();        // 注释1        mChoreographer.postCallback(                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        if (!mUnbufferedinputdispatch) {            scheduleConsumeBatchedinput();        }        notifyRendererOfFramePending();        pokeDrawLockIfNeeded();    }}

注释1处方法参数mTraversalRunnable是一个Runnable,进入查看它的run方法:

final class TraversalRunnable implements Runnable {    @OverrIDe    public voID run() {        doTraversal();    }}

继续追踪进入doTraversal方法

voID doTraversal() {    ...	performTraversals();    ...}

进入performTraversals方法, 正式进入VIEw绘制的三大流程

private voID performTraversals() {	...	// 执行测量	performMeasure(xxx)	...	// 执行布局	performlayout(xxx);	...	// 执行绘制	performDraw();	...}

绘制入口的简单小结

ActivityThread.handleResumeActivity()WindowManagerImpl.addVIEw(decorVIEw, layoutParams)WindowManagerGlobal.addVIEw()VIEwRootImpl.addVIEw()3.2绘制涉及的类及方法VIEwRootImpl.setVIEw(decorVIEw, layoutParams, parentVIEw)VIEwRootImple.requestLayout()–>scheduleTraversals()–>doTraversal()–>performTraversals()3.3绘制三大步骤测量:VIEwRootImpl.performMeasure()布局:VIEwRootImpl.performlayout()绘制:VIEwRootImpl.performDraw()结语

VIEwRootImpl是连接WindowManager和DecorVIEw的纽带,VIEw绘制的三大流程均是通过它来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorVIEw添加到Window中,同时会创建VIEwRootImple对象,并将VIEwRootImpl和DecorVIEw建立关联。到performTraversals方法的主要调用流程大致如下图:


VIEw的具体绘制从VIEwRootImpl的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个VIEw绘制出来,其中measure用来测量VIEw的宽和高,layout用来确定VIEw在父容器中的放置位置,而draw则负责将VIEw绘制在屏幕上。

总结

以上是内存溢出为你收集整理的Android UI绘制流程前奏全部内容,希望文章能够帮你解决Android UI绘制流程前奏所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1060864.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存