欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android UI绘制流程及原理

程序员文章站 2022-04-13 12:08:11
...

1.前言

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 UI绘制流程及原理
总结一下,View是如何添加到屏幕窗口上的,主要分为三个步骤:

  • 创建顶层布局容器DecorView
  • 在顶层布局中加载基础布局容器ViewGroup
  • 将ContentView添加到基础布局中的FrameLayout中

3.View的绘制流程

3.1绘制入口

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

Android UI绘制流程及原理

在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()

结语

本文分析好比开胃菜,有了这部分的基本认识,后续就可以正式进入View绘制的三大流程了。

相关标签: UI绘制