Android 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方法:

@Override
public 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的绘制流程,进入该方法:

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

进入scheduleTravels方法:

@UnsupportedAppUsage
void 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绘制在屏幕上。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/my_csdnboke/article/details/106202599
今日推荐