Activity的setContentView渲染的原理

Posted 花姓-老花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Activity的setContentView渲染的原理相关的知识,希望对你有一定的参考价值。

通过源码得知Activity、PhoneWindow和Window之间的关系(API25)


PhoneWindow是唯一实现Window的具体实现类,在PhoneWindow中有一个DecorView对象

DecorView对象是所有应用窗口(即Activity界面)的根View,DecorView是FrameLayout的子类,对FrameLayout进行装饰或渲染,是所有应用窗口的根View


setContentView

Activity中setContentView直接调用getWindow().setContentView(),PhoneWindow又是Window的实现类,所以就来具体看PhoneWindow的setContentView方法

setContentView(int layoutResID)源码

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)  //mContentParent是放置窗口内容(Activity界面)的视图,如果mContentParent为空
            installDecor(); 
         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.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) 
            cb.onContentChanged();
        
        mContentParentExplicitlySet = true;
    

上面知道,如果mContentParent为空时,就调用installDecor来生成mContentParent的View(即是Activity界面的根视图)

加载完布局视图后,最后onContentChanged方法,发现在PhoneWindow并没重写Window的这个方法,onContentChange方法在Window中是空方法

public void onContentChanged();
当Activity的布局改动时,即setContentView()或者addContentView()方法执行完毕时就会调用该方法。


private void installDecor() 
        mForceDecorInstall = false;
        if (mDecor == null)    //mDecor是个顶层视图
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) 
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            
         else 
            mDecor.setWindow(this);
        
        if (mContentParent == null) 
            mContentParent = generateLayout(mDecor);

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

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

            if (decorContentParent != null) 
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) 
                    mDecorContentParent.setWindowTitle(mTitle);
                

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) 
                    if ((localFeatures & (1 << i)) != 0) 
                        mDecorContentParent.initFeature(i);
                    
                

                mDecorContentParent.setUiOptions(mUiOptions);

                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) 
                    mDecorContentParent.setIcon(mIconRes);
                 else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                        mIconRes == 0 && !mDecorContentParent.hasIcon()) 
                    mDecorContentParent.setIcon(
                            getContext().getPackageManager().getDefaultActivityIcon());
                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) 
                    mDecorContentParent.setLogo(mLogoRes);
                

                // Invalidate if the panel menu hasn't been created before this.
                // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                // being called in the middle of onCreate or similar.
                // A pending invalidation will typically be resolved before the posted message
                // would run normally in order to satisfy instance state restoration.
                PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) 
                    invalidatePanelMenu(FEATURE_ACTION_BAR);
                
             else 
                mTitleView = (TextView) findViewById(R.id.title);
                if (mTitleView != null) 
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) 
                        final View titleContainer = findViewById(R.id.title_container);
                        if (titleContainer != null) 
                            titleContainer.setVisibility(View.GONE);
                         else 
                            mTitleView.setVisibility(View.GONE);
                        
                        mContentParent.setForeground(null);
                     else 
                        mTitleView.setText(mTitle);
                    
                
            

           .......
          
            
        
    

上面知道,如果mContentParent 为空,通过generateLayout(mDecor)进行赋值,参数mDecor是Activity顶层布局(也可以理解为一个ViewGroup)视图

protected ViewGroup generateLayout(DecorView decor) 
        // Apply data from current theme.

        TypedArray a = getWindowStyle();   //mCurrentParent(ViewGroup)装饰的风格

        .....
        

        WindowManager.LayoutParams params = getAttributes();   //设置布局相关属性

        // Non-floating windows on high end devices must put up decor beneath the system bars and
        // therefore must know about visibility changes of those.  设置相关设备非浮动窗口的显示情况
        if (!mIsFloating && ActivityManager.isHighEndGfx()) 
            if (!targetPreL && a.getBoolean(
                    R.styleable.Window_windowDrawsSystemBarBackgrounds,
                    false)) 
                setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                        FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
            
            if (mDecor.mForceWindowDrawsStatusBarBackground) 
                params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
            
        
       .....

        

        // Inflate the window decor.

        int layoutResource;   //布局xml资源
        int features = getLocalFeatures(); //得到此窗口正常工作的特性。给出了requestfeature()集,被处理的只有这个窗口本身,而不它的容器
        ......
        mDecor.startChanging(); //标记顶层DecorView开始改变
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  //得到上面设置的xml Layout资源,然后添加到DecorView根视图上

        ViewGroup contentParent = (ViewGroup)findViewById(ID_android_CONTENT);
        if (contentParent == null) 
            throw new RuntimeException("Window couldn't find content container view");
        

       	....... //DecorView相关属性设置
        mDecor.finishChanging();

        return contentParent;
    

上面主要根据窗口风格类型选择不窗口的根布局文件,mDecor做为根视图将该窗口根布局添加进去,然后获取id为ID_ANDROID_CONTENT,然后返回给mContentParent对象,从上面就知道installDecor方法实质就是产生mDecor和mContentParent对象


在Activity中设置标题状态(feature)或者相关主题(theme),有两种配置方式

1、通过xml形式android:theme属性,通过这里的getWindowStyle()获取的

2、通过代码形式requestWindowFeature(),在这里通过getLocalFeature()获取的

设置Activity的属性时必须在setContentView方法之前调用requestFeature()方法的原因


setContentView方法工作流程:

Activity的setContentView主要是把Activity布局文件(xml或代码形式)添加到顶层视图上

1、先创建一个DecorView对象mDecor作为所有应用窗口(即Activity界面)的根视图

2、根据不同Feature等风格选择不同窗口修饰布局文件

3、将Activity布局文件添加到mContentParent上

public void setContentView(int layoutResID) 
        ......
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) 
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
         else 
            mLayoutInflater.inflate(layoutResID, mContentParent);
        
       ......
    


以上是关于Activity的setContentView渲染的原理的主要内容,如果未能解决你的问题,请参考以下文章

setContentView()给当前Activity加载布局出错

Android-Activity中setContentView流程解析

Activity setContentView背后的一系列源码分析

Activity setContentView背后的一系列源码分析

Android源码解析Activity#setContentView()方法

将 View 从 setContentView 传递给非 Activity 类