setContentView源码分析[通俗易懂]

setContentView源码分析[通俗易懂]publicclassActivityTestextendsAppCompatActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);}}↓…

大家好,又见面了,我是你们的朋友全栈君。

1 从我们自己新建的一个Activity的setContentView 就开始

public class ActivityTest extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

    }

}
                ↓
                ↓ setContentView(R.layout.activity_test);
                ↓
public class AppCompatActivity extends ... {

     public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
     }


                ↓
                ↓ getDelegate().setContentView(layoutResID);先找getDelegate()
                ↓ getDelegate()也在AppCompatActivity 中
                ↓
 @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
                ↓
                ↓  getDelegate() = AppCompatDelegate.create(this, this);
                ↓
public abstract class AppCompatDelegate {
  public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }   
}

所以最后调用的是 AppCompatDelegateImpl 中的 setContentView

2 AppCompatDelegateImpl 中的  setContentView 方法:

class AppCompatDelegateImpl extends ...{

    public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

}

  接着去看 ensureSubDecor 方法:

2.1 ensureSubDecor 

private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
            this.mSubDecor = this.createSubDecor();//核心代码

           //省略其它代码。。。
        }

    }

2.2 createSubDecor 

 private ViewGroup createSubDecor() {
        ✍这里是获取Activity的theme,并根据主题风格为subDecor确定加载哪个布局 
        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
        if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        } else {
             

            this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
            a.recycle();
            this.mWindow.getDecorView(); ---> ✍✔️step1 创建DecirView
            LayoutInflater inflater = LayoutInflater.from(this.mContext);
            ViewGroup subDecor = null; ✍✔️Step2 开始
            if (!this.mWindowNoTitle) {
                if (this.mIsFloating) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                    
                } else if (this.mHasActionBar) {
                    
                    subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null); 
                    
                }
            } else {
                if (this.mOverlayActionMode) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                } else {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                }

                //其它代码省略...
                
                
            }
            ✍ 通过上面subDecor = inflate(布局文件)可以看出,subDecor 的布局文件是下面四种布局文件之一:
                  1  abc_dialog_title_material
                  2  abc_screen_toolbar
                  3  abc_screen_simple_overlay_action_mode
                  4  abc_screen_simple
            if (subDecor == null) {
                ✍ 如果subDecor为空就抛出异常,这个异常看起来是不是很熟悉 
                throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
            } else {
                if (this.mDecorContentParent == null) {
                    this.mTitleView = (TextView)subDecor.findViewById(id.title);
                }

                ViewUtils.makeOptionalFitsSystemWindows(subDecor);
                 //✍ 上面说了 subDecor 是四个布局文件中的一个创建,
                 // ✍每个布局文件都有一action_bar_activity_content   
                ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
                ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
                if (windowContentView != null) {
                    while(windowContentView.getChildCount() > 0) {
                        View child = windowContentView.getChildAt(0);
                        windowContentView.removeViewAt(0);
                        contentView.addView(child);
                    }

                    windowContentView.setId(-1);
                    contentView.setId(16908290);--->✍✔️Step2 结束 contentView的id被设置成16908290
                    if (windowContentView instanceof FrameLayout) {
                        ((FrameLayout)windowContentView).setForeground((Drawable)null);
                    }
                }

                this.mWindow.setContentView(subDecor);---> ✍✔️Step3 把subDecor加载到DecorView布局文件中的容器中
                contentView.setAttachListener(new OnAttachListener() {
                    public void onAttachedFromWindow() {
                    }

                    public void onDetachedFromWindow() {
                        AppCompatDelegateImpl.this.dismissPopups();
                    }
                });
                return subDecor; ✍ 把subDecor返回赋值给mSubDecor
            }
        }
    }

总结一下createSubDecor方法,一共做了三件事:

1  this.mWindow.getDecorView(); 创建Decorview,并为它加载一个布局文件,找到这个布局文件中 R.id.content 的容器,赋值给 mContentParent。

    这样我们就准备好了一个DecorView和其布局中id为R.id.content 的容器。

2 给ViewGroup subDecor根据主题、style选择合适布局文件并加载到subDecor中:

        ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);  

        ViewGroup windowContentView =  (ViewGroup)this.mWindow.findViewById(R.id.content);➱  这里就是上一步里面那个布局文件的R.id.content 容器  

        windowContentView.setId(View.NO_ID);  ➱ 把windowContentView的id设置为View.NO_ID 即  -1

        contentView.setId(android.R.id.content); 把contentView 的id设置为R.id.content

    这样我们准备好了subDecor和其布局中 id为action_bar_activity_content的容器,并把这个容器的id改成 R.id.content 

3  this.mWindow.setContentView(subDecor); 将第2步的subDecor添加到 第1步准备好的DecorView的容器mContentParent中。

 

下面分析这三步骤

Step1 this.mWindow.getDecorView();  this.mWindow 这个是啥呢?看下面

 AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
        ......

       mWindow 的初始化是在AppCompatDelegateImpl构造函数里
                ↓
       this.mWindow = window;

        .....
       
    }
        想要知道mWindow是啥就要找到AppCompatDelegateImpl(context,window,callback)这个
        构造函数初始化的时候传入的window是啥
        还记得最开始我们从setContentView点进来的代码么
        
                ↓
                ↓  
                ↓
*********************************************************************** 
public class ActivityTest extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

    }

}
                ↓
                ↓ setContentView(R.layout.activity_test);
                ↓
public class AppCompatActivity extends ... {

     public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
     }


                ↓
                ↓ getDelegate().setContentView(layoutResID);先找getDelegate()
                ↓ getDelegate()也在AppCompatActivity 中
                ↓
 @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
                ↓
                ↓  getDelegate() = AppCompatDelegate.create(this, this);
                ↓
public abstract class AppCompatDelegate {
  public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        在这里初始化的,activity就是AppCompatActivity ,window就是activity.getWindow()
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }   
}
***************************************************************************************

window就是AppCompatActivity.getWindow(),但是AppCompatActivity中没有getWindow()方法,
getWindow()是在其父类Activity中实现
public class Activity extends ... ... {

       private Window mWindow;
        
      final void attach(Context context, ......) {
        attachBaseContext(context);

           

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        ......
       }

      public @Nullable Window getWindow() {
            return mWindow;
       }
}



最后找到window就是PhoneWindow对象。

那就到里PhoneWindow面看一下 getDecorView()是如何实现的

PhoneWindow.java: 
@Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();  --->  ✍调用installDecor()
        }
        return mDecor;
    }

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1); ---> ✍ generateDecor就是创建DecorView对象
           
            ....省略其它代码....
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); ---> ✍ 给DecorView对象加载布局文件
                                                              并准备好R.id.content容器               
            ....省略其它代码.....
            }
}

先来看 mDecor = generateDecor(-1); generateDecor方法很简单就是创建了一个DecorView对象并返回赋值给 mDecor

 protected DecorView generateDecor(int featureId) {
      
        ...其它代码省略...        

        return new DecorView(context, featureId, this, getAttributes());
    }

接着看 mContentParent = generateLayout(mDecor); 

1:mContentParent是一个ViewGroup;

2 :generateLayout(mDecor);将上面刚刚创建的DecorView作为参数传了进去

protected ViewGroup generateLayout(DecorView decor) {
      ✍ 获取windowStyle,给DecorView加载哪种布局就是根据这个来判断的!
      TypedArray a = getWindowStyle();        

        ...其它代码省略...

        // Inflate the window decor.

        ✍ 这里精简保留的,layoutResource = R.layout.布局文件 
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
              
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            
            layoutResource = R.layout.screen_progress;
           
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
           
            if (mIsFloating) {
               
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
           
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
             
            if (mIsFloating) {
              
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
             
            layoutResource = R.layout.screen_simple;
            
        }

        mDecor.startChanging();

       ✍ layoutResource就是上面根据WindowStyle决定的布局文件之中的一个,
            然后加载到mDecor即DecorView中
                    ↓
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
       
       ✍ 而上面布局文件中它们都有一个id是content的
       ✍ 而且这个findViewById是在PhoneWindow直接使用?它是Activity?文章最后分析
                      ↓
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        
        return contentParent; ← ✍返回DecorView的布局文件中id为content的控件
    }

到这里 step1 this.mWindow.getDecorView() 就完成了。PhoneWindow新建了一个DecorView并为其加载好布局文件,并将布局文件中R.id.content容器准备好。

 

Step2 具体分析已在上面代码中注释

 

step3 this.mWindow.setContentView(subDecor)

接着我们就来看看3 this.mWindow.setContentView(subDecor);同样是this.mWindow我们就到PhoneWindow中找setContentView方法

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
           ✍还记得 step1 的时候准备好的mContentParent,现在就是把subDecor加载到其中
            ↓  
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

 mContentParent.addView(view, params);  

|— mContentParent 就是上面DecorView中加载布局文件的 content 控件

|— view 就是我们传递进来的subDecor

到这里Step3 就结束了。

 

再来看最开始的setContentView

 public void setContentView(int resId) {
        this.ensureSubDecor(); ✍ 我们上面这么久的调用就是走完这一行代码,想不到吧。

        ✍找到id为R.id.content的控件,即subDecor布局中的控件
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(R.id.content);

        contentParent.removeAllViews();

        ✍把我们的布局文件加载到R.id.content的控件中即subDecor布局中
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);

        this.mOriginalWindowCallback.onContentChanged();
    }

我们自己的布局文件被加载到了subDecor布局中id为 action_bar_activity_content的 ContentFrameLayout中。

而subDecor被加载到了DecorView布局中id为content的FrameLayout中;

DecorView是由PhoneWindow 创建

PhoneWindow 在Activity attach方法中初始化。

这样整个加载过程就串起来了。

是时候放上这样一张图了

setContentView源码分析[通俗易懂]

DecorView加载的xml文件为下面中的一个:

screen_swipe_dismiss 

screen_title_icons 

screen_progress

screen_custom_title

screen_action_bar

screen_title

screen_simple_overlay_action_mode

screen_simple

而他们都有一个id为content的FrameLayout控件用来加载subDcor.

subDcor加载的xml文件为下面中的一个:

abc_dialog_title_material
abc_screen_toolbar
abc_screen_simple_overlay_action_mode
abc_screen_simple

而他们都有一个id为action_bar_activity_content 的 ContentFrameLayout 控件用来加载我们自定义的布局文件,就是我们创建Activity时候创建的xml文件。

 

上面还有一个问题PhoneWindow为啥可以直接findViewById? 点进去就会发现是在其父类Window中实现的

public abstract class Window {
     @Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

    public abstract View getDecorView();

}

getDecorView的具体实现又回到了PhoneWindow 中;

    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

这个方法看起来是不是很熟悉,就是我们初始化DecorView对象的方法,返回的就是我们初始化并加载了布局的DecorView.

所以PhoneWindow进行findViewById其实就是对其持有的DecorView进行操作。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/153274.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 计算机错误代码0X000000be,Win7系统出现蓝屏代码0x000000BE的解决方法「建议收藏」

    计算机错误代码0X000000be,Win7系统出现蓝屏代码0x000000BE的解决方法「建议收藏」蓝屏是我们日常使用电脑的时候经常会碰到的故障,这不就有很多win732位系统用户反映说电脑出现蓝屏错误代码0x000000BE,该怎么解决这样的问题呢,接下来给大家讲解一下Win7系统出现蓝屏代码0x000000BE的解决方法吧。原因分析:0x000000BE错误表示硬件设备的驱动程序试图向只读内存错误地写入数据。这个错误一般是因为硬件设备驱动程序存在BUG或安装不正确引起的。解决方法:按开机按…

    2022年10月8日
    3
  • 2021版idea激活码99年csdn_在线激活

    (2021版idea激活码99年csdn)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月28日
    89
  • Java集合List转树结构工具类[通俗易懂]

    Java集合List转树结构工具类[通俗易懂]业务场景:菜单树、组织架构树…..前端要求数据结构为树结构,而后端查出来的是一条一条的数据集,每次都要各种递归遍历很麻烦,特此写了一个工具类来解决.三个注解:importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***@a

    2022年7月16日
    25
  • Nginx安装

    Nginx安装

    2021年8月22日
    96
  • 它们的定义UIAlertView

    它们的定义UIAlertView

    2022年1月8日
    38
  • 业务逻辑漏洞总结[通俗易懂]

    业务逻辑漏洞总结[通俗易懂]逻辑漏洞简介逻辑漏洞就是指攻击者利用业务/功能上的设计缺陷,获取敏感信息或破坏业务的完整性。一般出现在密码修改、越权访问、密码找回、交易支付金额等功能处。逻辑漏洞的破坏方式并非是向程序添加破坏内容,而是利用逻辑处理不严密或代码问题或固有不足。操作上并不影响程序运行,在逻辑上是顺利执行的。这种漏洞一般的防护手段或设备无法阻止,因为走的都是合法流量。也没有防护标准。逻辑漏洞分类越权漏洞密码修改密码找回验证码漏洞支付漏洞短信轰炸投票/积分/抽奖逻辑漏洞重要性常见的OWASP

    2022年5月24日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号