Activity、FragmentActivity和AppCompatActivity的区别

Activity

Activity是最基础的一个,是其它类的直接或间接父类。
Activity中只能使用系统自带的host Fragment(API Level 11中加入),对应getFragmentManager方法来控制Activity和Fragment之间的交互。

FragmentActivity

在v4包中引入FragmentActivity,FragmentActivity间接继承自Activity,并提供了对v4包中support Fragment的支持。
在FragmentActivity中必须使用getSupportFragmentManager方法来处理support Fragment的交互。也可以处理support Fragment的嵌套使用。

Known limitations:

*  When using the `<fragment>` tag, this implementation can not use the parent view's ID as the new fragment's ID. 
You must explicitly specify an ID (or tag) in the `<fragment>`.

AppCompatActivity

AppCompatActivity继承自FragmentActivity,同时取代了ActionBarActivity。
AppCompatActivity支持ActionBar功能,同时更推荐使用ToolBar。AppCompatActivity为支持Material Design风格控件提供了便利。

使用场景

参考stack overflow中一个回答。
Activity is the baseline. Every activity inherits from Activity, directly or indirectly.

FragmentActivity is for use with the backport of fragments found in the support-v4 and support-v13 libraries. The native implementation of fragments was added in API Level 11, which is lower than your proposed minSdkVersion values. The only reason why you would need to consider FragmentActivity specifically is if you want to use nested fragments (a fragment holding another fragment), as that was not supported in native fragments until API Level 17.

AppCompatActivity is from the appcompat-v7 library. Principally, this offers a backport of the action bar. Since the native action bar was added in API Level 11, you do not need AppCompatActivity for that. However, current versions of appcompat-v7 also add a limited backport of the Material Design aesthetic, in terms of the action bar and various widgets. There are pros and cons of using appcompat-v7, well beyond the scope of this specific Stack Overflow answer.

ActionBarActivity is the old name of the base activity from appcompat-v7. For various reasons, they wanted to change the name. Unless some third-party library you are using insists upon an ActionBarActivity, you should prefer AppCompatActivity over ActionBarActivity.

So, given your minSdkVersion in the 15-16 range:

  • If you want the backported Material Design look, use AppCompatActivity

  • If not, but you want nested fragments, use FragmentActivity

  • If not, use Activity

Just adding from comment as note: AppCompatActivity extends FragmentActivity, so anyone who needs to use features of FragmentActivity can use AppCompatActivity.

AppCompatActivity中AppCompat系列组件的构造及替换

AppCompatActivity中通过AppCompatDelegate来扩展Activity。AppCompatDelegate可以在任一Activity中使用,需要与合适的生命周期方法挂钩。具体可参考官方文档https://developer.android.com/reference/android/support/v7/app/AppCompatDelegate或者AppCompatDelegate的源码注释。

在AppCompatActivity的onCreate方法中,调用AppCompatDelegate(基类)的create方法创建实例,并调用delegate.installViewFactory()设置factory。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        ......
        super.onCreate(savedInstanceState);
    }

创建AppCompatDelegate子类实例的代码如下,根据手机的安卓版本来创建不同的子类实例

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

以android 6.0(API 23)为例,来跟踪AppCompatDelegate实例对象的创建。AppCompatDelegateImplV23间接继承AppCompatDelegateImplV9,并实现了LayoutInflater.Factory2接口。

AppCompatDelegateImplV9的installViewFactory调用了 LayoutInflaterCompat.setFactory2,设置了自身的Factory2实现。

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

AppCompatActivity创建view时,通过PhoneWindow->LayoutInflater(createViewFromTag)进入了其实现Factory2的onCreateView方法。可参考LayoutInflater setFactory进阶中setContentView调用流程分析

    /**
     * From {@link LayoutInflater.Factory2}.
     */
    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // First let the Activity's Factory try and inflate the view
        final View view = callActivityOnCreateView(parent, name, context, attrs);
        if (view != null) {
            return view;
        }

        // If the Factory didn't handle it, let our createView() method try
        return createView(parent, name, context, attrs);
    }

如果AppCompatActivity并没有设置Factory,就会调用Delegate实例的createView方法创建view。

@Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    : shouldInheritContext((ViewParent) parent);
        }

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

根据上述代码可知,Delegate实例调用了AppCompatViewInflater的createView来完成具体view的绘制。

public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

在上述代码中可看到,AppCompatActivity中TextView等组件,被替代为AppCompatTextView等,从而可以利用AppCompat系列组件的特性。但是书写自定义view时,需要手动使用AppCompat组件。

参考AppCompatTextView的提示
This will automatically be used when you use TextView in your layouts and the top-level activity / dialog is provided by appcompat. You should only need to manually use this class when writing custom views.

参考文档:
https://stackoverflow.com/questions/31297246/activity-appcompatactivity-fragmentactivity-and-actionbaractivity-when-to-us

推荐阅读更多精彩内容