Android setContentView流程[通俗易懂]

2022-09-13 11:21:41 浏览数 (2)

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

更新于:2022-01-09 Android-30 implementation ‘androidx.appcompat:appcompat:1.2.0’ setContentView并没有将view添加到屏幕上,只是创建了DecorView,xml添加到DecorView而已。

文章目录

    • MainActivity 继承Activity的setContentView流程
      • 1) MainActivity 继承至 Activity
      • 1.1) Activity#setContentView
      • 1.1.1) PhoneWindow#setContentView
      • 1.1.1.1) PhoneWindow#installDecor
      • 1.1.1.1.1) PhoneWindow#generateDecor
      • 1.1.1.1.2) PhoneWindow#generateLayout
      • 1.1.1.1.2.1) screen_simple.xml
      • 1.1.1.1.2.2) DecorView#onResourcesLoaded
      • 1.1.1.2) LayoutInflater#inflate
      • 1.1.1.2.1) LayoutInflater#createViewFromTag
      • 1.1.1.2.1.1) LayoutInflater#tryCreateView
      • 1.1.1.2.1.1.1) AppCompatDelegateImpl#onCreateView
      • 1.1.1.2.1.1.1.1) AppCompatDelegateImpl#createView
      • 1.1.1.2.1.1.1.1.1) AppCompatViewInflater#createView
      • 1.1.1.2.1.2) LayoutInflater#onCreateView
      • 1.1.1.2.1.2.1) PhoneLayoutInflater#onCreateView
      • 1.1.1.2.1.2.1.1) LayoutInflater#onCreateView
      • 1.1.1.2.1.2.1.1.1) LayoutInflater#createView
      • 2) 为什么在 requestWindowFeature 在 setContentView之后调用,会报错,必须在之前调用才可以?
    • MainActivity 继承AppCompatActivity的setContentView流程
      • 1) MainActivity 继承至 AppCompatActivity
      • 1.1) AppCompatActivity#onCreate
      • 1.1.1) AppCompatDelegateImpl#installViewFactory
      • 1.2) AppCompatActivity#setContentView
      • 1.2.1) AppCompatDelegateImpl#setContentView
      • 1.2.1.1) AppCompatDelegateImpl#ensureSubDecor
      • 1.2.1.1.1) PhoneWindow#getDecorView
      • 1.2.1.1.2) abc_screen_simple.xml
      • 1.2.1.1.3) abc_screen_content_include.xml
      • 1.2.1.2) LayoutInflater#inflate
      • 1.2.1.2.1) LayoutInflater#createViewFromTag
      • 1.2.1.2.1.1) LayoutInflater#tryCreateView
      • 1.2.1.2.1.1.1) AppCompatDelegateImpl#onCreateView
      • 1.2.1.2.1.1.1.1) AppCompatDelegateImpl#createView
      • 1.2.1.2.1.1.1.1.1) AppCompatViewInflater#createView
      • 1.2.1.2.1.2) LayoutInflater#onCreateView
      • 1.2.1.2.1.2.1) PhoneLayoutInflater#onCreateView
      • 1.2.1.2.1.2.1.1) LayoutInflater#onCreateView
      • 1.2.1.2.1.2.1.1.1) LayoutInflater#createView
      • 继承Activity时,设置requestWindowFeature(Window.FEATURE_NO_TITLE)可以生效,当继承AppCompatActivity时,就无效了?
      • 打印TextView,却输出的不是TextView?
        • 1) activity_main.xml
        • 2) MainActivity.java
        • 3) 打印结果
        • 4) 分析
    • LayoutInflater.inflate中参数的作用
      • LayoutInflater的部分源码:
      • 效果演示:
        • inflate_layout.xml
        • activity_main.xml
        • MainActivity

MainActivity 继承Activity的setContentView流程

1) MainActivity 继承至 Activity

代码语言:javascript复制
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

1.1) Activity#setContentView

代码语言:javascript复制
/** * 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() 是 抽象类Window对象,其子类为 PhoneWindow
    // 这里调用的是 PhoneWindow的setContentView
    getWindow().setContentView(layoutResID);
    
    initWindowDecorActionBar();
}

1.1.1) PhoneWindow#setContentView

代码语言:javascript复制
@Override
public void setContentView(int layoutResID) { 
   
    // 初始化DecorView,并对 mContentParent 赋值 mContentParent就是一个View
    // mContentParent 是 R.layout.screen_simple资源文件 中 FramLayout 这个View
    // 后面会提到这里
    if (mContentParent == null) { 
   
        installDecor();
    } 
    ......
    // 将 layoutResID 即 R.layout.activity_main 填入到 mContentParent,渲染操作
    mLayoutInflater.inflate(layoutResID, mContentParent);
    ......
    // 设置标志位
    // 如:当调用requsetWindowFeature时,如果在setContentView之后调用
    // 就会抛出异常,因为这里设置了值
    mContentParentExplicitlySet = true;
}

1.1.1.1) PhoneWindow#installDecor

代码语言:javascript复制
private void installDecor() { 
   
    ......
    // This is the top-level view of the window, containing the window decor.
    // mDecor 是一个 DecorView 即顶层的 View,它包含 window
    mDecor = generateDecor(-1);
	
    ......
    // 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.
    // mContentParent 是 ViewGroup 即 它是用来放置view的
    // 要么是mDecor本身,要么是mDecor的子代
    mContentParent = generateLayout(mDecor);
    ......
}

1.1.1.1.1) PhoneWindow#generateDecor

代码语言:javascript复制
protected DecorView generateDecor(int featureId) { 
   
    ......
    // 初始化DecorView
    return new DecorView(context, featureId, this, getAttributes());
}

1.1.1.1.2) PhoneWindow#generateLayout

初始化 mContentParent 根据不同的主题选择不同的资源文件。

代码语言:javascript复制
protected ViewGroup generateLayout(DecorView decor) { 
   
    ......
    // 根据不同的主题,选择不同的资源文件
    // 这里以 R.layout.screen_simple 文件为例说明
    layoutResource = R.layout.screen_simple;
	
    ......
    // 将 R.layout.screen_simple 添加到 DecorView 即顶层的 View
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	
    // 获取 id 为 ID_ANDROID_CONTENT 即 com.android.internal.R.id.content
    // contentParent 就是 R.layout.screen_simple资源文件 中 FramLayout 这个View
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
	
    return contentParent;
}

1.1.1.1.2.1) screen_simple.xml

代码语言:javascript复制
<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>

1.1.1.1.2.2) DecorView#onResourcesLoaded

代码语言:javascript复制
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { 
   
    ......
    // 加载资源文件 如上方提到的 R.layout.screen_simple
    final View root = inflater.inflate(layoutResource, null);
        
    ......
    // 将 资源文件 加载到 DecorView 中
    addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    ......
}

1.1.1.2) LayoutInflater#inflate

渲染操作

代码语言:javascript复制
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { 
   
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, 
    boolean attachToRoot) { 
   
	
    ......
    return inflate(parser, root, attachToRoot);
    ......
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   
	
    ......
    // 通过反射创建View
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    ......
    ViewGroup.LayoutParams params = null;
    ......
    params = root.generateLayoutParams(attrs);
    ......
    // 创建子View
    rInflateChildren(parser, temp, attrs, true);
    ......
    root.addView(temp, params);
    ......
}

1.1.1.2.1) LayoutInflater#createViewFromTag

代码语言:javascript复制
private View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs) { 
   
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs, boolean ignoreThemeAttr) { 
   
    ......
    // 创建view
    View view = tryCreateView(parent, name, context, attrs);
    
    // 如果 view 为null,使用默认的创建 view 的方式创建
    // 也就是通过 AppCompatDelegateImpl 来创建 View
    if (view == null) { 
   
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try { 
   
        	// 判断是否为sdk自己的
            if (-1 == name.indexOf('.')) { 
   
                view = onCreateView(context, parent, name, attrs);
            } else { 
   
                view = createView(context, name, null, attrs);
            }
        } finally { 
   
            mConstructorArgs[0] = lastContext;
        }
    }

    return view;
}

1.1.1.2.1.1) LayoutInflater#tryCreateView

创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null

当继承Activity时,并没有默认设置 factory2,当继承 AppCompatActivity 时,在 super.onCreate(savedInstanceState) 进行了设置 factory2

代码语言:javascript复制
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) { 
   
    if (name.equals(TAG_1995)) { 
   
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    if (mFactory2 != null) { 
   
        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;
}

1.1.1.2.1.1.1) AppCompatDelegateImpl#onCreateView

代码语言:javascript复制
@Override
public final View onCreateView(View parent, String name, 
	Context context, AttributeSet attrs) { 
   
    return createView(parent, name, context, attrs);
}

1.1.1.2.1.1.1.1) AppCompatDelegateImpl#createView

从代码看,是调用 AppCompatViewInflater的createView方法

代码语言:javascript复制
@Override
public View createView(View parent, final String name, 
	@NonNull Context context, @NonNull AttributeSet attrs) { 
   
    ......
    mAppCompatViewInflater = new AppCompatViewInflater();
    ......
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP,   
            true,              
            VectorEnabledTintResources.shouldBeUsed() 
    );
}

1.1.1.2.1.1.1.1.1) AppCompatViewInflater#createView

从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView

代码语言:javascript复制
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;

    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;
        case "EditText":
            view = createEditText(context, attrs);
            verifyNotNull(view, name);
            break;
        case "Spinner":
            view = createSpinner(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageButton":
            view = createImageButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckBox":
            view = createCheckBox(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RadioButton":
            view = createRadioButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckedTextView":
            view = createCheckedTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "AutoCompleteTextView":
            view = createAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "MultiAutoCompleteTextView":
            view = createMultiAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RatingBar":
            view = createRatingBar(context, attrs);
            verifyNotNull(view, name);
            break;
        case "SeekBar":
            view = createSeekBar(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ToggleButton":
            view = createToggleButton(context, attrs);
            verifyNotNull(view, name);
            break;
        default:
            view = createView(context, name, attrs);
    }
    
    ......
    
    return view;
}

当为 TextView 的时候,就会被替换为 AppCompatTextView

代码语言:javascript复制
@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) { 
   
    return new AppCompatTextView(context, attrs);
}

1.1.1.2.1.2) LayoutInflater#onCreateView

代码语言:javascript复制
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,    
	@NonNull String name, @Nullable AttributeSet attrs) 
	throws ClassNotFoundException { 
   
	
    return onCreateView(parent, name, attrs);
}

protected View onCreateView(View parent, String name, AttributeSet attrs)
	throws ClassNotFoundException { 
   
    // 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
    return onCreateView(name, attrs);
}

1.1.1.2.1.2.1) PhoneLayoutInflater#onCreateView

代码语言:javascript复制
private static final String[] sClassPrefixList = { 
   
    "android.widget.",
    "android.webkit.",
    "android.app."
};

@Override protected View onCreateView(String name, AttributeSet attrs) 
	throws ClassNotFoundException { 
   
	
    for (String prefix : sClassPrefixList) { 
   
        try { 
   
            View view = createView(name, prefix, attrs);
            if (view != null) { 
   
                return view;
            }
        } catch (ClassNotFoundException e) { 
   
            // In this case we want to let the base class take a crack
            // at it.
        }
    }

    return super.onCreateView(name, attrs);
}

1.1.1.2.1.2.1.1) LayoutInflater#onCreateView

代码语言:javascript复制
public final View createView(String name, String prefix, AttributeSet attrs)
	throws ClassNotFoundException, InflateException { 
   
    Context context = (Context) mConstructorArgs[0];
    if (context == null) { 
   
        context = mContext;
    }
    return createView(context, name, prefix, attrs);
}

1.1.1.2.1.2.1.1.1) LayoutInflater#createView

代码语言:javascript复制
// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name, 
	@Nullable String prefix, @Nullable AttributeSet attrs) 
	throws ClassNotFoundException, InflateException { 
   

	clazz = Class.forName(prefix != null ? (prefix   name) : name, false,
               mContext.getClassLoader()).asSubclass(View.class);

    constructor = clazz.getConstructor(mConstructorSignature);
    
    final View view = constructor.newInstance(args);
}

2) 为什么在 requestWindowFeature 在 setContentView之后调用,会报错,必须在之前调用才可以?

代码语言:javascript复制
public class MainActivity extends Activity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
// 在 setContentView 之前调用
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        setContentView(R.layout.activity_main);
        
// error
// requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
}

报错内容:

查看requestWindowFeature的代码逻辑:

调用的是PhoneWindow的requestFeature方法,里面会判断mContentParentExplicitlySet是否已经设置过值了,如果为true,就会报错!

代码语言:javascript复制
public boolean requestFeature(int featureId) { 
   
    if (mContentParentExplicitlySet) { 
   
        throw new AndroidRuntimeException(
        	"requestFeature() must be called before adding content");
    }
    ......
}

其实,在PhoneWindow#setContentView方法的最后一行,会设置 mContentParentExplicitlySet 为ture,所以在之后调用,就会报错了。

MainActivity 继承AppCompatActivity的setContentView流程

1) MainActivity 继承至 AppCompatActivity

代码语言:javascript复制
public class MainActivity extends AppCompatActivity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

1.1) AppCompatActivity#onCreate

在onCreate中,会设置factory,去看 installViewFactory

代码语言:javascript复制
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) { 
   
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

1.1.1) AppCompatDelegateImpl#installViewFactory

进行设置 Factory2

如:当创建 TextView 的时候,会因为这里设置了 Factory2,而把 TextView 替换为 AppCompatTextView

代码语言:javascript复制
@Override
public void installViewFactory() { 
   
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) { 
   
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else { 
   
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { 
   
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                      " so we can not install AppCompat's");
        }
    }
}

1.2) AppCompatActivity#setContentView

调用的是AppCompatDelegateImpl实现类中的 setContentView

代码语言:javascript复制
@Override
public void setContentView(@LayoutRes int layoutResID) { 
   
    initViewTreeOwners();
    getDelegate().setContentView(layoutResID);
}

1.2.1) AppCompatDelegateImpl#setContentView

代码语言:javascript复制
@Override
public void setContentView(int resId) { 
   
    // 确保ActionBar的特有UI结构构建完毕
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    // 确保ContentView的所有Child全部被移除干净
    contentParent.removeAllViews();
    // 将画面的内容布局解析并添加到ContentView下
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

1.2.1.1) AppCompatDelegateImpl#ensureSubDecor

代码语言:javascript复制
private ViewGroup createSubDecor() { 
   
    .....
	
    mWindow.getDecorView();  // 同MainActivity继承Activity的逻辑,看那个就行
	
    ......
	
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
	
    final ContentFrameLayout contentView = (ContentFrameLayout) 
    	subDecor.findViewById(R.id.action_bar_activity_content);
	
	final ViewGroup windowContentView = (ViewGroup) 
		mWindow.findViewById(android.R.id.content);
	
	......

    if (windowContentView != null) { 
   
       // There might be Views already added to the Window's content view 
       // so we need to
       // migrate them to our content view
       while (windowContentView.getChildCount() > 0) { 
   
           final View child = windowContentView.getChildAt(0);
           windowContentView.removeViewAt(0);
           contentView.addView(child);
       }

       // Change our content FrameLayout to use the android.R.id.content id.
       // Useful for fragments.
       // 把 android.R.id.content 这里,改为 NO_ID
       windowContentView.setId(View.NO_ID);  
       
       // R.id.action_bar_activity_content 改为 android.R.id.content
       // 就是做了下替换操作
       contentView.setId(android.R.id.content); 

       // The decorContent may have a foreground drawable 
       // set (windowContentOverlay).
       // Remove this as we handle it ourselves
       if (windowContentView instanceof FrameLayout) { 
   
           ((FrameLayout) windowContentView).setForeground(null);
       }
    }
   
    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);
    ......
    return subDecor;
}

1.2.1.1.1) PhoneWindow#getDecorView

代码语言:javascript复制
public final @NonNull View getDecorView() { 
   
    if (mDecor == null || mForceDecorInstall) { 
   
        // 同MainActivity继承Activity的逻辑,看那个就行
        installDecor();
    }
    return mDecor;
}

1.2.1.1.2) abc_screen_simple.xml

代码语言:javascript复制
<androidx.appcompat.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@ id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <androidx.appcompat.widget.ViewStubCompat
        android:id="@ id/action_mode_bar_stub"
        android:inflatedId="@ id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</androidx.appcompat.widget.FitWindowsLinearLayout>

1.2.1.1.3) abc_screen_content_include.xml

代码语言:javascript复制
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.appcompat.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>

1.2.1.2) LayoutInflater#inflate

渲染操作

代码语言:javascript复制
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { 
   
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   
    ......
    return inflate(parser, root, attachToRoot);
    ......
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   
    ......
    // 通过反射创建View
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    ......
    ViewGroup.LayoutParams params = null;
    ......
    params = root.generateLayoutParams(attrs);
    ......
    // 创建子View
    rInflateChildren(parser, temp, attrs, true);
    ......
    root.addView(temp, params);
    ......
}

1.2.1.2.1) LayoutInflater#createViewFromTag

代码语言:javascript复制
private View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs) { 
   
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs, boolean ignoreThemeAttr) { 
   
    ......
    
    // 创建view
    View view = tryCreateView(parent, name, context, attrs);
    
    // 如果 view 为null,使用默认的创建 view 的方式创建
    // 也就是通过 AppCompatDelegateImpl 来创建 View
    if (view == null) { 
   
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try { 
   
        	// 判断是否为sdk自己的 
            if (-1 == name.indexOf('.')) { 
   
                // sdk的
                view = onCreateView(context, parent, name, attrs);
            } else { 
   
                // 自己的,或者封装的
                view = createView(context, name, null, attrs);
            }
        } finally { 
   
            mConstructorArgs[0] = lastContext;
        }
    }

    return view;
}

1.2.1.2.1.1) LayoutInflater#tryCreateView

创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null

代码语言:javascript复制
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) { 
   
    if (name.equals(TAG_1995)) { 
   
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    if (mFactory2 != null) { 
   
        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;
}

1.2.1.2.1.1.1) AppCompatDelegateImpl#onCreateView

代码语言:javascript复制
@Override
public final View onCreateView(View parent, String name, 
	Context context, AttributeSet attrs) { 
   
    return createView(parent, name, context, attrs);
}

1.2.1.2.1.1.1.1) AppCompatDelegateImpl#createView

从代码看,是调用 AppCompatViewInflater的createView方法

代码语言:javascript复制
@Override
public View createView(View parent, final String name, 
	@NonNull Context context, @NonNull AttributeSet attrs) { 
   
    ......
    mAppCompatViewInflater = new AppCompatViewInflater();
    ......
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP,   
            true,              
            VectorEnabledTintResources.shouldBeUsed() 
    );
}

1.2.1.2.1.1.1.1.1) AppCompatViewInflater#createView

从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView

代码语言:javascript复制
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;

    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;
        case "EditText":
            view = createEditText(context, attrs);
            verifyNotNull(view, name);
            break;
        case "Spinner":
            view = createSpinner(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageButton":
            view = createImageButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckBox":
            view = createCheckBox(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RadioButton":
            view = createRadioButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckedTextView":
            view = createCheckedTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "AutoCompleteTextView":
            view = createAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "MultiAutoCompleteTextView":
            view = createMultiAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RatingBar":
            view = createRatingBar(context, attrs);
            verifyNotNull(view, name);
            break;
        case "SeekBar":
            view = createSeekBar(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ToggleButton":
            view = createToggleButton(context, attrs);
            verifyNotNull(view, name);
            break;
        default:
            view = createView(context, name, attrs);
    }
    
    ......
    
    return view;
}

当为 TextView 的时候,就会被替换为 AppCompatTextView

代码语言:javascript复制
@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) { 
   
    return new AppCompatTextView(context, attrs);
}

1.2.1.2.1.2) LayoutInflater#onCreateView

代码语言:javascript复制
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
        @NonNull String name, @Nullable AttributeSet attrs)
        throws ClassNotFoundException { 
   
    return onCreateView(parent, name, attrs);
}

protected View onCreateView(View parent, String name, AttributeSet attrs)
        throws ClassNotFoundException { 
   
    // 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
    return onCreateView(name, attrs);
}

1.2.1.2.1.2.1) PhoneLayoutInflater#onCreateView

代码语言:javascript复制
private static final String[] sClassPrefixList = { 
   
    "android.widget.",
    "android.webkit.",
    "android.app."
};

@Override protected View onCreateView(String name, AttributeSet attrs) 
	throws ClassNotFoundException { 
   
    for (String prefix : sClassPrefixList) { 
   
        try { 
   
            View view = createView(name, prefix, attrs);
            if (view != null) { 
   
                return view;
            }
        } catch (ClassNotFoundException e) { 
   
            // In this case we want to let the base class take a crack
            // at it.
        }
    }

    return super.onCreateView(name, attrs);
}

1.2.1.2.1.2.1.1) LayoutInflater#onCreateView

代码语言:javascript复制
public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException { 
   
    Context context = (Context) mConstructorArgs[0];
    if (context == null) { 
   
        context = mContext;
    }
    return createView(context, name, prefix, attrs);
}

1.2.1.2.1.2.1.1.1) LayoutInflater#createView

代码语言:javascript复制
// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name,
    @Nullable String prefix, @Nullable AttributeSet attrs)
    throws ClassNotFoundException, InflateException { 
   

	clazz = Class.forName(prefix != null ? (prefix   name) : name, false,
               mContext.getClassLoader()).asSubclass(View.class);

    constructor = clazz.getConstructor(mConstructorSignature);
    
    final View view = constructor.newInstance(args);
}

继承Activity时,设置requestWindowFeature(Window.FEATURE_NO_TITLE)可以生效,当继承AppCompatActivity时,就无效了?

需要使用supportRequestWindowFeature,因为AppCompatActivity类里面会覆盖设置。

代码语言:javascript复制
public class MainActivity extends AppCompatActivity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);

// requestWindowFeature(Window.FEATURE_NO_TITLE);

        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.activity_main);
    }
}

当调用requestWindowFeature,设置的代码如下,其中使用的mLocalFeatures这个:

代码语言:javascript复制
public boolean requestFeature(int featureId) { 
   
    final int flag = 1<<featureId;
    mFeatures |= flag;
    mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
    return (mFeatures&flag) != 0;
}

当调用supportRequestWindowFeature,设置的代码如下,使用的mWindowNoTitle,标志位改变了

代码语言:javascript复制
@Override
public boolean requestWindowFeature(int featureId) { 
   
    featureId = sanitizeWindowFeatureId(featureId);

    if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) { 
   
        return false; // Ignore. No title dominates.
    }
    if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) { 
   
        // Remove the action bar feature if we have no title. 
        // No title dominates.
        mHasActionBar = false;
    }

    switch (featureId) { 
   
        case FEATURE_SUPPORT_ACTION_BAR:
            throwFeatureRequestIfSubDecorInstalled();
            mHasActionBar = true;
            return true;
        case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
            throwFeatureRequestIfSubDecorInstalled();
            mOverlayActionBar = true;
            return true;
        case FEATURE_ACTION_MODE_OVERLAY:
            throwFeatureRequestIfSubDecorInstalled();
            mOverlayActionMode = true;
            return true;
        case Window.FEATURE_PROGRESS:
            throwFeatureRequestIfSubDecorInstalled();
            mFeatureProgress = true;
            return true;
        case Window.FEATURE_INDETERMINATE_PROGRESS:
            throwFeatureRequestIfSubDecorInstalled();
            mFeatureIndeterminateProgress = true;
            return true;
        case Window.FEATURE_NO_TITLE:
            throwFeatureRequestIfSubDecorInstalled();
            mWindowNoTitle = true;
            return true;
    }

    return mWindow.requestFeature(featureId);
}

打印TextView,却输出的不是TextView?

1) activity_main.xml
代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@ id/test_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello world"
        />
    
</LinearLayout>
2) MainActivity.java
代码语言:javascript复制
public class MainActivity extends AppCompatActivity { 
   
    private static final String TAG = "AAAAAAA";

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

        TextView textView1 = findViewById(R.id.test_tv);
        Log.d(TAG, "textView1: "   textView1);

        TextView textView2 = new TextView(this);
        Log.d(TAG, "textView2: "   textView2);
    }
}
3) 打印结果
代码语言:javascript复制
D/AAAAAAA: textView1: com.google.android.material.textview.MaterialTextView{ 
   d43c607 V.ED..... ......ID 0,0-0,0 #7f080195 app:id/test_tv}
D/AAAAAAA: textView2: android.widget.TextView{ 
   c9ba334 V.ED..... ......ID 0,0-0,0}
4) 分析

当继承 AppCompatActivity 时,Activity的 super.onCreate(savedInstanceState) 中会进行默认设置 factory2,然后在执行 LayoutInflater#createViewFromTag 方法时,其中的 tryCreateView 方法会使用到factory2 当factory2不为空时,就会用factory2去创建View,这个view是把TextView替换为了 AppCompatTextView。

LayoutInflater.inflate中参数的作用

LayoutInflater的部分源码:

1)当root不为空,attachToRoot为true,会执行一次addView 2)当root不为空,attachToRoot为false,会获取到属性 3)当root为空,attachToRoot为false,会直接return,属性没有获取

代码语言:javascript复制
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   

	......

    // Temp is the root view that was found in the xml
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    ViewGroup.LayoutParams params = null;

    if (root != null) { 
   
        if (DEBUG) { 
   
            System.out.println("Creating params from root: "   root);
        }
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) { 
   
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }

    if (DEBUG) { 
   
        System.out.println("-----> start inflating children");
    }

    // Inflate all children under temp against its context.
    rInflateChildren(parser, temp, attrs, true);

    if (DEBUG) { 
   
        System.out.println("-----> done inflating children");
    }

    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) { 
   
        root.addView(temp, params);
    }

    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) { 
   
        result = temp;
    }
   
 	......
 	
	return result;
}

效果演示:

inflate_layout.xml
代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="@color/teal_200"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@ id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
activity_main.xml
代码语言:javascript复制
<?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:id="@ id/activity_linear_layout">

</LinearLayout>
MainActivity
代码语言:javascript复制
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity { 
   

    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        LinearLayout linear_layout = findViewById(R.id.activity_linear_layout);

        // 正常
        // LayoutInflater.from(this).inflate(R.layout.inflate_layout, 
        // linear_layout, true);


        // 报错
        // 当执行inflate的时候,已经addView了,当再次addView调用,会报错,
        // 一个View只能有一个parent
        // The specified child already has a parent. 
        // You must call removeView() on the child's parent first.
        // View view = LayoutInflater.from(this).inflate(
        // R.layout.inflate_layout, linear_layout, true);
        // linear_layout.addView(view);


        // 正常
        // 第三个参数为false,不会去addView,所以,当调用addView的时候,就没什么问题
        // View view = LayoutInflater.from(this).inflate(
        // R.layout.inflate_layout, linear_layout, false);
        // linear_layout.addView(view);


        // 能显示,但显示不正常,inflate_layout没有父容器了
        // inflate_layout的布局无效,由inflate_layout的内容即button的大小决定
        View view = LayoutInflater.from(this).inflate(
        	R.layout.inflate_layout, null, false);
        linear_layout.addView(view);
    }
}

正常的:

不带有布局参数的:

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

0 人点赞