美文网首页安卓
setContentView源码分析

setContentView源码分析

作者: w达不溜w | 来源:发表于2022-02-23 12:38 被阅读0次

    直奔主题setContentView:AppCompatActivity#setContentView

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
      initViewTreeOwners();
      //getDelegate()具体实现类是AppCompatDelegateImpl
      getDelegate().setContentView(layoutResID);
    }
    

    进入AppCompatDelegateImpl#setContentView

    @Override
    public void setContentView(int resId) {
      //检测mSubDecor视图是否已经创建,没有则创建mSubDecor视图,同时将视图添加到Window上
      ensureSubDecor();
      //获取根布局
      ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
      contentParent.removeAllViews();
      //将我们的布局添加到根布局上
      LayoutInflater.from(mContext).inflate(resId, contentParent);
      mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    

    查看ensureSubDecor方法

    private void ensureSubDecor() {
      //...
      if (!mSubDecorInstalled) {
        //创建mSubDecor视图
        mSubDecor = createSubDecor();
        mSubDecorInstalled = true;
      }
      // ...
    }
    

    createSubDecor()创建mSubDecor视图

    private ViewGroup createSubDecor() {
      //获得主体样式
      TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //我们自定义主体必须继承AppCompat
      if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException(
          "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
      }
    
      //...
    
      // 确认Window上是否已安装DecorView,没有则创建DecorView并添加到Window上
      // 分析①:mWindow.getDecorView()
      mWindow.getDecorView();
        
      final LayoutInflater inflater = LayoutInflater.from(mContext);
      ViewGroup subDecor = null;
      
      //...
      // 填充subDecor
      subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);  
      //...
        //找到id为action_bar_activity_content的contentView
      final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
        //找到PhoneWindow中的ContentView
      final ViewGroup windowContentView = (ViewGroup)mWindow.findViewById(android.R.id.content);
      if (windowContentView != null) {
        // 如果已经有视图添加到窗口,移除并添加到contentView中
        while (windowContentView.getChildCount() > 0) {
          final View child = windowContentView.getChildAt(0);
          windowContentView.removeViewAt(0);
          contentView.addView(child);
        }
    
        //标记android.R.id.content视图的id为NO_ID
        //并将我们布局父容器FrameLayout设置为android.R.id.content
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
        
      }
    
      // 将我们添加视图的父视图添加到Window上
      mWindow.setContentView(subDecor);
    
      //...
    
      return subDecor;
    }
    

    分析①:mWindow.getDecorView() 创建DecorView

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

    installDecor()具体实现

    // This is the top-level view of the window, containing the window decor.
    //DecorView是Window的顶层View
    private DecorView mDecor;
    
    private void installDecor() {
      mForceDecorInstall = false;
      if (mDecor == null) {
            //DecorView为空则需要新建
          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);
         //...
      }
    }
    

    通过调用generateDecor创建DecorView

    protected DecorView generateDecor(int featureId) {
      //...
      //new一个DecorView
      return new DecorView(context, featureId, this, getAttributes());
    }
    

    创建DecorView后,再调用generateLayout

    protected ViewGroup generateLayout(DecorView decor) {
      TypedArray a = getWindowStyle();
      //如果设置了Window_windowNoTitle,则会调用requestFeature(FEATURE_NO_TITLE),其它同理
      if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
      } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
      }
      //是否设置全屏
      if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
      }
      //...
      int layoutResource;
      int features = getLocalFeatures();
      //根据设置的features来加载对应的布局
      if()else if()else{
      // screen_simple就是DecorView加载的布局
        layoutResource = R.layout.screen_simple;
      }
      //把布局解析加载到DecorView,用的addView() 
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      //从系统的layoutResource找一个id是android.R.id.content的FrameLayout
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      
      return contentParent;
    }
    

    generateLayout方法会根据用户设置的主题去设置对应的feature,根据feature来选择加载对应的布局文件,这就是为什么要在setContentView之前调用requestFeature的原因。

    看下布局R.layout.screen_simple

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
        <ViewStub android:id="@+id/action_mode_bar_stub"
                  android:inflatedId="@+id/action_mode_bar"
                  android:layout="@layout/action_mode_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:theme="?attr/actionBarTheme" />
        <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>
    

    FrameLayout里面id是@android:id/content,我们setContentView的内容就是要添加到这个FrameLayout中。

    LayoutInflater解析xml并创建View实例分析

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
       return inflate(resource, root, root != null);
    }
    
      LayoutInflater.from(mContext).inflate(resId, contentParent)
    > LayoutInflater#inflate() xml解析
    > LayoutInflater#createViewFromTag()
    > LayoutInflater#tryCreateView()
    > AppCompatDelegateImpl#onCreateView()
    > AppCompatDelegateImpl#createView()
    > AppCompatViewInflater#createView()
    

    tryCreateView()

     public final View tryCreateView(@Nullable View parent, @NonNull String name,
            @NonNull Context context,
            @NonNull AttributeSet attrs) {
            View view;
            //可以通过反射的方式修改mFactory2,从而实现换肤插件
            if (mFactory2 != null) {
                //最终调用AppCompatViewInflater中的createView方法
                 //可以通过自定义mFactory2让其生成用户所需要的控件
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
    
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
    
            return view;
        }
    

    View最终实例化:

    final View createView(View parent, final String name, @NonNull Context context,
                @NonNull AttributeSet attrs, boolean inheritContext,
                boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
      //...
      View view = null;
    
      // We need to 'inject' our tint aware Views in place of the standard framework versions
      //如果是系统控件,则直接new出来
      switch (name) {
        case "TextView":
          view = createTextView(context, attrs);
          verifyNotNull(view, name);
          break;
        case "ImageView":
          view = createImageView(context, attrs);
          verifyNotNull(view, name);
          break;
        case "Button":
          view = createButton(context, attrs);
          verifyNotNull(view, name);
          break;
        //...
        default:
          view = createView(context, name, attrs);
      }
      if (view == null && originalContext != context) {
        // 没有匹配,也就是不是系统控件,到则通过反射创建
        view = createViewFromTag(context, name, attrs);
      }
        //...
      return view;
    }
    

    createViewFromTag最终会调用createViewByPrefix方法

    private View createViewByPrefix(Context context, String name, String prefix)
               throws ClassNotFoundException, InflateException {
     //先从构造缓存中获取
     Constructor<? extends View> constructor = sConstructorMap.get(name);
     try {
       if (constructor == null) {
         // Class not found in the cache, see if it's real, and try to add it
         Class<? extends View> clazz = Class.forName(
           prefix != null ? (prefix + name) : name,
           false,
           context.getClassLoader()).asSubclass(View.class);
               //利用反射构建一个构造函数
         constructor = clazz.getConstructor(sConstructorSignature);
         sConstructorMap.put(name, constructor);
       }
       constructor.setAccessible(true);
       //利用反射创建View实例
       return constructor.newInstance(mConstructorArgs);
     } catch (Exception e) {
       // We do not want to catch these, lets return null and let the actual LayoutInflater
       // try
       return null;
     }
    }
    

    解析xml去匹配系统的View,匹配到通过new创建实例,没有匹配到(如自定义View)则通过反射去创建实例。

    相关文章

      网友评论

        本文标题:setContentView源码分析

        本文链接:https://www.haomeiwen.com/subject/nusolrtx.html