Activity的setContentView

0 阅读13分钟

1. 当MainActivity直接继承自Activity时

1.1 方法调用流程

  • onCreate 方法中调用:

    • MainActivityonCreate 方法中,通常会首先调用 setContentView 方法来设置该 Activity 要显示的内容视图。例如:
    import android.app.Activity;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    
  • 传入布局资源引用或视图对象:

    • setContentView 方法可以接受两种类型的参数:

      • 布局资源引用(如上述代码中的 R.layout.activity_main),这是通过 Android 系统自动生成的资源索引来指向一个预先定义好的 XML 布局文件;

      • 视图对象(比如通过代码动态创建的一个 LinearLayout 视图并添加了一些子视图后的对象)。

1.2 基于布局资源引用执行情况

  • 资源索引与布局文件对应:

    • 当传入布局资源引用(如 R.layout.activity_main)时,Android 系统会根据这个资源索引找到对应的 XML 布局文件(这里假设是 activity_main.xml)。这个 XML 布局文件位于项目的 src/main/res/layout 文件夹下,它定义了 Activity 的布局结构,包括各种视图组件(如按钮、文本框、列表等)的排列方式、属性设置等。
  • 布局解析与视图实例化:

    • Android 系统会对找到的 XML 布局文件进行解析,将其中定义的各种视图组件按照 XML 中的标签和属性信息进行实例化。例如,如果 activity_main.xml 中有一个 <TextView> 标签定义了一个文本视图,系统会根据这个标签创建一个实际的 TextView 实例,并根据标签中的属性(如 text 属性设置文本内容、textColor 属性设置文本颜色等)为这个实例设置相应的属性值。
  • 添加到窗口视图体系:

    • 经过解析和实例化后的所有视图组件会被添加到 PhoneWindows 中的 mContentParent 容器中(mContentParentPhoneWindows 中用于容纳 Activity 主要内容视图的容器)。具体来说,系统会将这些视图组件按照 XML 布局文件中的布局方式(如线性布局、相对布局等)进行排列和调整,然后添加到 mContentParent,以便在屏幕上正确显示 Activity 的内容。

1.3 基于视图对象执行情况

  • 视图对象创建与准备:

    • 如果传入的是视图对象(如通过代码动态创建的视图),那么在调用 setContentView 之前,开发者需要先自行创建并准备好这个视图对象。例如,可以创建一个 LinearLayout 视图对象,并向其添加一些子视图(如 TextViewButton 等),设置好它们的属性(如文本内容、点击事件监听器等)。
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // 创建一个线性布局对象
            LinearLayout linearLayout = new LinearLayout(this);
            linearLayout.setOrientation(LinearLayout.VERTICAL);
    
            // 创建一个文本视图对象并添加到线性布局中
            TextView textView = new TextView(this);
            textView.setText("这是通过代码设置的视图内容");
            linearLayout.addView(textView);
    
            setContentView(linearLayout);
        }
    }
    
  • 添加到窗口视图体系:

    • 当调用 setContentView 方法传入这个准备好的视图对象时,系统会直接将这个视图对象添加到 PhoneWindows 中的 mContentParent 容器中,以便在屏幕上显示出这个由开发者自行创建和设置的视图内容。

1.4 源码分析

看一下Activity-setContentView方法:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

内部调用了 getWindows.setContentView(xxx)

Windows 表示一个窗口的概念,Android 中不管是Activity,dialog,还是 Toast 它们的视图都是附加在 Windows 上,因此可以称 windows 是View的直接管理者。而Windows有实现类,即PhoneWindows

我们接着去看 PhoneWindowssetContentView()

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); //关注点
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
     mLayoutInflater.inflate(layoutResID, mContentParent);
}

内部执行了一个判断,然后调用 **installDecor() ** 方法。

我们接着去看 **installDecor()**方法:

    //方法虽然很长,但是其实主要就分成两个部分,重点是generateDecor和generateLayout
    private void installDecor() {
        mForceDecorInstall = false;
        
        
        //第一个部分开始
        //干了两件事情,1创建mDecor 2将mDecor和Window绑定
        if (mDecor == null) {
            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);//根据mDecor创建mContentParent
            
            //下面基本就是根据各种FLAG和style设置页面的各种属性了
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            ... ...忽略
        }
        //第二个部分结束
    }


mDecor 是 windows 唯一视图,也就是我们 mContentParent 的父视图。简称 DecorView

mContentParent 即放置我们自己布局的容器,你可以理解为,它是我们的根容器

我们接着去看 generateDecor() 方法

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

直接new了一个 DecorView,返回回去看 installDecor() 中的关注点2-generateLayout().

进入 generateLayout() 方法:

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        /*
        ... 根据各种flag和属性设置页面的样式
        */

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                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;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        
        //上面一大串,根据各种属性获取到了一个layoutResource,这个layoutResource是系统中已经定义好的,这样就能看出我们平时设置的各种style 和 flag都是怎么作用的了。

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //将layoutResource加载到了mDecor的最底层。

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//获取了id是ID_ANDROID_CONTENT的view
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks(contentParent);
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

上述代码简化后

protected ViewGroup generateLayout(DecorView decor) {
   //1
   TypedArray a = getWindowStyle();
   if(xx)else if(xxx)        
   else {
       layoutResource = R.layout.screen_simple;
   }
   //2
   mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
         ..
   //3
   ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   return contentParent;
}

  1. 显示获取当前主题,然后开始判断究竟要用那个布局

  2. 将其加载到 DecorView

  3. 通过 findViewById(内部就是DecorView.findViewById)获取 R.id.content,并返回此viewGroup。

等等,这个 R.layout.screen_simple 是什么

就是我们DecorView中加载的布局,具体如下。

如上图所示,我们的布局最终会被添加到这个根布局content中。

1.5 总结

我们接下来将上面的分析整体走一遍:

  • 当我们调用Activity的 setContentView 时,内部其实是执行了 PhoneWindows(windows的唯一实例)的 setContenView()

  • PhoneWindowssetContentView() 内部会先判断当前有没有布局容器 contentParent,也即就是有没有 DecorView,如果没有,执行 installDecor() 去初始化我们的 DecorViewcontentParent

  • installDecor() 方法里面,会先判断有没有 DecorView,如果没有,先new一个出来,然后判断有没有 contentParent(承载我们自己布局的ViewGroup),没有的话,就去根据当前主题,选择一个布局,并将其当做我们的根布局添加到 DecorView 中,再将其中的子view,即R.id.content这个view赋值给我们的 contentParent

  • 最后 PhoneWindows-setContentView() 方法接下来就可以将我们自己的布局 inflate 进这个根布局的 contentParent 里了

juejin.cn/post/728331…

juejin.cn/post/689745…

2. 当MainActivity继承AppCompatActivity

2.1 前言

在 Android 应用开发的世界里,setContentView 几乎是每个开发者都会接触到的方法。它的作用至关重要——负责将视图(View)或布局(Layout)展示在屏幕上。尽管这看起来是一个简单直接的操作,但其背后实际上隐藏着 Android 系统中复杂而精妙的窗口管理和视图渲染机制。

我们一起梳理并深入探讨 Android 的窗口管理和视图展示原理,希望能够为大家带来新的理解和启发。

2.2 AppCompatActivity

一般而言大家都会如此使用setConentView

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(mContentLayoutId);
}

当然你也可以在这么使用:

 class TestActivity : AppCompatActivity(R.layout.activity_test)

们看看AppCompatActivitysetContentView藏着什么:

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

可以看到在AppCompatActivity中,使用的getDelegate,这看起来像是委托啊!点下去看看:

public AppCompatDelegate getDelegate() {
    if (this.mDelegate == null) {
        this.mDelegate = AppCompatDelegate.create(this, this);
    }

    return this.mDelegate;
}

我们找到了全新的AppCompatDelegate!官方说明:

此类表示一个委托,您可以使用该委托将 AppCompat 的支持 扩展到任何 Activity

只能 Activity 与一个 AppCompatDelegate 实例链接,因此应保留从 create(Activity, AppCompatCallback) 返回的实例,直到 Activity 被销毁。

我们可以在这里看到setContentView

但是AppCompatDelegate是一个抽象类,我们可以很轻松的找到它的实现类AppCompatDelegateImpl

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

分析ensureSubDecor

    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor(); // ⭐重点主线流程
 
            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (mDecorContentParent != null) {
                    mDecorContentParent.setWindowTitle(title);
                } else if (peekSupportActionBar() != null) {
                    peekSupportActionBar().setWindowTitle(title);
                } else if (mTitleView != null) {
                    mTitleView.setText(title);
                }
            }
 
            applyFixedSizeWindow();
 
            onSubDecorInstalled(mSubDecor);
 
            mSubDecorInstalled = true;//⭐这个flag参数
 
            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mIsDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

mSubDecor是一个ViewGroup类型的对象,等待后面分析createSubDecor()的时候就能见分晓

分析contentParent

可以看到,我们提供的LayoutId,最后会添加到contentParent这个View上

那么mSubDecor是从哪儿来的?看到调用方法的名字,ensure sub decor ,子装饰视图,想必在这里。我们继续往下看:

private void ensureSubDecor() {
    if (!this.mSubDecorInstalled) {
        this.mSubDecor = this.createSubDecor();
        CharSequence title = this.getTitle();
        //......略

}

看样子藏在createSubDecor

分析createSubDecor

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    //**省略 获取当前上下文的主题属性,设置对应的样式
    a.recycle();

    // 确保窗口已安装其装饰
    ensureWindow();
    mWindow.getDecorView();

    // 获取布局填充器
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;

    // 根据是否有标题和是否为浮动窗口来决定使用哪个布局
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // 如果是浮动窗口,则使用对话框标题装饰
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);
            //**
        } else if (mHasActionBar) {
            // 如果有动作栏,则使用特定主题创建布局
            // 使用主题化上下文填充视图并设置为内容视图
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);
            //**
        }
    } else {
        // 根据是否覆盖动作模式选择不同布局
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_screen_simple_overlay_action_mode, null);
            //**
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            //**
        }
    }
    //**
    // 将窗口的内容视图设置为subDecor
    mWindow.setContentView(subDecor);
    //**
    return subDecor;
}
 

可以看到这个方法返回一个配置好的 subDecorsubDecor使用的是系统的布局,根据配置的不同,使用了不同的xml

返回给我们用于添加LayoutId,但是它是如何显示的还是不清楚,但是注意到mWindow.setContentView(subDecor);

我们点下去一看,发现 来到Window类:

//Window
public abstract void setContentView(View view);

Window的实现类-PhoneWindow

ensureWindow()
    private void ensureWindow() {
        // We lazily fetch the Window for Activities, to allow DayNight to apply in
        // attachBaseContext
        if (mWindow == null && mHost instanceof Activity) {
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }

首先我们要明确AppCompatActivity是继承自Activity的,所以window也是在attach方法中创建的,在AppCompatDelegateImpl中也维护了一个Window类型的变量是mWindow,就是通过这个ensureWindow方法经过检查后赋值过来的。

说白了ensureWindow方法就是把AppCompatActivity中的Window对象赋值到AppCompatDelegateImpl对象中,当然对window设置的callBack啥的也换成AppCompatDelegateImpl中的。

2.3 PhoneWindow

我们找到PhoneWindow

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    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 {
        // 将视图添加到内容父视图中
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

我们看看如何初始化mContentParent,走进installDecor()的内部

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 创建窗口装饰视图
        mDecor = generateDecor(-1);
        //**
    } else {
        mDecor.setWindow(this);
    }

    if (mContentParent == null) {
        // 生成并设置内容布局
        mContentParent = generateLayout(mDecor);
        //**
    } else {
        //**
    }
    // 省略涉及到过渡管理器和动画的配置
}

在这里我们可以看到两个,generateDecor,但是在createSubDecor中我们已经创建过了:

protected ViewGroup generateLayout(DecorView decor) {
    // ... 省略了一部分属性设置代码 ...

    // 根据窗口特性选择布局资源
    int layoutResource;
    int features = getLocalFeatures();
    // 根据不同的特性标志选择不同的布局资源
    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))) !=
            && (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();
    // 使用LayoutInflater加载布局资源
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 获取内容父视图
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    // ... 省略了其他设置代码 ...

    // 装饰视图完成变化
    mDecor.finishChanging();

    return contentParent;
}

在这个方法中,会根据不同的特性标志选择不同的布局资源,但是这些布局都有一个显著的特点。他们都有一个id为content的FrameLayout。就是那个常用的android.R.id.content,表示窗口的 内容区域

 <FrameLayout
     android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent" /

2.4 流程总结

至此,我们知道了PhoneWindowsetContentView中的contentParent来自哪里:

那我们也是知道了AppCompatDelegate中的setContentViewcontentParent来自哪里:

  1. 在活动的 onCreate 方法中调用 setContentView,传入布局资源ID或者直接传入一个视图(View)对象。

  2. AppCompatDelegate中调用Window.setContentView

  3. PhoneWindow 对象负责创建和管理顶层视图容器,DecorView。如果 DecorView 还未创建,Window 会通过调用 generateDecor 方法来创建它。DecorView中一定有一个ID为android.R.id.content的FrameLayout。

  4. AppCompatDelegateImpl将视图添加到android.R.id.content

2.5 与Activity的setContentView对比

Activity的setContentView 调用PhoneWindow 再调用到decorView里会又一个screen_simple.xml,这个xml有viewStub和fragmentLayout组成,我们的布局就会加载到这个fragment上。

主要区别对比

流程方面的区别:

AppCompatActivity前面流程基本和Activity一致,唯一的区别就是AppCompatActivity会在fragment上再嵌套一层,然后将原来view一个个删掉,然后添加到新的布局上面,然后就会去解析xml布局,LayoutInflater这里进行加载。

代码实现方面的区别:

  • Activity.setContentView直接将视图添加到Window上

  • AppCompatActivity.setContentView()借助AppCompatActivity的Delegate代理类,将要显示的视图加入到代理层视图,代理层视图再添加到Window上;

总结

AppCompatActivityActivity 的增强版,提供更广泛的兼容性和现代特性支持。

Activity 是底层实现,适合轻量、简单的场景,但在功能上不如 AppCompatActivity 丰富。

本系列【安卓基础重点知识】是刚开始学习android的时候记录的,其中部分内容来自网页,忘记记录来源了,如需添加引用,联系我即可