Android UI绘制流程及原理

问题

首先回忆我们在Android开发比较常用的方法,首先我们会创建Activity,然后在Activity的onCreate方法中,会调用setContentView方法并传入布局资源id,这样我们的界面就显示出来了,那么Activity.setContentView是怎么做的呢?

源码分析

那么我们从源码中来分析,setContentView中为我们做了什么

/**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

在源码中我们可以看到里面又调用了getWindow().setContentView(layoutResID),接着我们看getWindow()又是什么

/**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }
    
@UnsupportedAppUsage
    private Window mWindow;
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
......
......
}

PhoneWindow

接着我们看Window的声明,在第二段的文档注释中,我们可以看到,The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.关于该抽象类Window,仅存在唯一的实现类android.view.PhoneWindow, 当需要一个Window对象时,可以通过PhoneWindow进行创建实例。因为Window仅存在唯一的实现类PhoneWindow,在Activity中的Window即为PhoneWindow的实例,那么问题又来了, 是什么时候创建了PhoneWindow呢?接着在Activity的源码中,我们继续找

@UnsupportedAppUsage
    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) {
        attachBaseContext(context);

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

        mWindow = new PhoneWindow(this, window, activityConfigCallback);//实例化PhoneWindow
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        ...
        ...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        ...
        ...
    }

可以看到在activity的attach方法中实例化了PhoneWindow,并赋值给了mWindow,这里attach方法是什么时候被调用的,就不做深入分析了,主要涉及ActivityThread方面的源码,接着我们看PhoneWindow的setContentView方法做了什么

@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();//初始化DecorView
        } 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下面
        }
        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);//初始化DecorView
            ....
            ....
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//初始化ContentParent,并挂在DecorView下面

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            ....
            ....
            ....
        }
    }
protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        } //做context的处理
        // 创建DecorView返回
        return new DecorView(context, featureId, this, getAttributes());
    }

protected ViewGroup generateLayout(DecorView decor) {
        ....
        ....
        // Inflate the window decor.

        int layoutResource;//contentParent的布局文件
        int features = getLocalFeatures();
        
        //根据不同特性,设置不同布局文件
        ....
        ....
        
        mDecor.startChanging();
        //加载contentParent布局文件,并挂载到DecorView下面
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //每个布局文件下都会有一个id 为 content的ViewGroup
        /**
         * The ID that the main layout in the XML layout file should have.
         */
        //public static final int 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");
        }

       ....
       ....

        mDecor.finishChanging();

        return contentParent;
    }

总结

  1. PhoneWindow的创建
    首先Activity创建的时候,ActivityThread会调用Activity的attach方法,attach方法内部会实例化一个PhoneWindow对象,并赋值给mWindow

  2. setContentView内部逻辑
    在Activity中调用setContentView,内部会调用mWindow对应setContentView方法,在PhoneWindow的setContentView方法中会初始化顶层View即DecorView,紧接着会根据不同主题特性,加载不同布局文件,但每个布局文件内都会有一个id为content的ViewGroup,通过findViewById找到该ViewGroup,并赋值给mContentParent, 随后会将我们在Activity中通过调用setContentView设置的layout布局资源,加载到mContentParent下面。

类图关系

分析到此,我们就清楚了Activity.setContentView是如何将layout文件显示到界面上的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容