Android setContentView流程

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

文章目录

MainActivity 继承Activity的setContentView流程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1) MainActivity 继承至 Activity

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

/**
 * 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

@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

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

protected DecorView generateDecor(int featureId) {
    ......
    // 初始化DecorView
    return new DecorView(context, featureId, this, getAttributes());
}

1.1.1.1.2) PhoneWindow#generateLayout

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

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

<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

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

渲染操作

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

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

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

@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方法

@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

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

@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}

1.1.1.2.1.2) LayoutInflater#onCreateView

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

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

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

// 通过反射创建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之后调用,会报错,必须在之前调用才可以?

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,就会报错!

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

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

@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

@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

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

1.2.1) AppCompatDelegateImpl#setContentView

@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

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

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

1.2.1.1.2) abc_screen_simple.xml

<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

<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

渲染操作

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

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

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

@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方法

@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

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

@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}

1.2.1.2.1.2) LayoutInflater#onCreateView

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

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

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

// 通过反射创建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类里面会覆盖设置。

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这个:

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,标志位改变了

@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
<?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
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) 打印结果
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,属性没有获取

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
<?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
<?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
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);
    }
}

正常的:
在这里插入图片描述
不带有布局参数的:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用 Android 标准 Camera API 打开相机的基本流程: 1. 添加权限 在 AndroidManifest.xml 文件中添加相机权限: ```xml <uses-permission android:name="android.permission.CAMERA" /> ``` 2. 创建预览布局 在您的 Activity 中添加一个 SurfaceView 作为相机预览的布局容器,例如: ```xml <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent"/> ``` 3. 创建相机实例 在您的 Activity 中创建相机实例并设置预览展示的 SurfaceView: ```java private Camera camera; private SurfaceView surfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); surfaceView = (SurfaceView) findViewById(R.id.surfaceView); // 打开相机 camera = Camera.open(); // 绑定预览视图 try { camera.setPreviewDisplay(surfaceView.getHolder()); } catch (IOException e) { e.printStackTrace(); } } ``` 4. 启动预览 调用 Camera.startPreview() 方法启动相机预览: ```java @Override protected void onResume() { super.onResume(); // 启动预览 camera.startPreview(); } ``` 5. 释放相机资源 在 Activity 销毁时释放相机资源: ```java @Override protected void onDestroy() { super.onDestroy(); // 释放相机资源 camera.stopPreview(); camera.release(); camera = null; } ``` 以上就是使用 Android 标准 Camera API 打开相机的基本流程。需要注意的是,上述代码仅适用于 Android 5.0 及以下版本。在 Android 6.0 及以上版本中,您需要动态请求相机权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值