The AppCompat framework has been available since the Support era in order to enable younger Versions of Android to run new features. But as AndroidX took over, AppCompat’s related classes migrated to the AndroidX library.

Any Android developer should be familiar with projects created on Android Studio using AppCompatActivity as the base class for the Activity by default. This class is arguably the most important in the entire AppCompat framework and is the starting point for our study of AppCompat today.

1. AppCompatActivity

It indirectly inherits from the Activity and other Activity feature classes in between, allowing activities running on earlier versions to have new features such as toolbars and dark themes.

AppCompatActivity extends FragmentActivity extends ComponentActivity extends ComponentActivity extends Activity*

class role
FragmentActivity Using FragmentController class fragments of AndroidX new component provides support, such as commonly used getSupportFragmentManager provides us () API.
androidx.activity.ComponentActivity Implement the ViewModel interface and work with the Lifecycle framework to support the ViewModel framework.
androidx.core.app.ComponentActivity Implement the Lifecycle interface and support the operation of Lifecycle framework through ReportFragment.

Let’s see how AppCompatActivity and Activity behave on the UI.

It doesn’t look very different from the comparison diagram, but it does look different from the UI tree diagram. For example, AppCompatActivity’s content area has a LinearLayout and ViewStub control above it, and AppCompatActivity has an AppCompatTextView instead of a TextView.

So how do these differences work and what do they mean?

When we talk about the AppCompatActivity implementation, we can’t help but mention the compatDelegate class, the main caretaker behind the compatActivity, which carries almost all of the work of the AppCompatActivity implementation.

For example, AppCompatActivity duplements the logic of setContentView(), and leaves it to the caretaker AppCompatDelegate to implement its own UI structure.

1.1 AppCompatDelegate

Let’s focus on the no. 1 job of the butler, setContentView(), which is broken down into the following tasks.

  • EnsureSubDecor () ensures that the specific UI structure of the ActionBar is created

  • RemoveAllViews () ensures that all children of the ContentView are removed clean

  • Inflate () parses the screen’s content layout and adds it under the ContentView

Step 1 ensureSubDecor() contains many contents and is divided into several sub-tasks. These include calling createSubDecor() to create the ActionBar-specific layout,setWindowTitle() to reflect the Activity title to the ToolBar, and applyFixedSizeWindow() to resize the DecorView.

The core is the createSubDecor() subtask. It needs to ensure that the ActionBar’s specific layout is created and associated with the Window’s DecorView.

  1. ensureWindow()

Get the Window reference to which the Activity belongs and add the window-related callback

  1. getDecorView()

Tell Window to create a DecorView. Mention generateLayout() for PhoneWindow, which creates different layout structures depending on the theme. For example, AppCompatActivity would parse screen_simple.xml to get the basic structure of the DecorView, which includes the root layout, the LinearLayout, The ViewStub used to map the ActionMode layout and the ID that hosts App content is ContentView

  1. inflate()

Get the layout of the ActionBar, mainly the abc_screen_tooltool. XML and abc_screen_content_include. XML files

  1. RemoveViewAt () and addView ()

Migrate the subviews of the ContentView to the ActionBar layout. Action_bar_activity_content, action_bar_Activity_Content, action_bar_Activity_Content, action_bar_Activity_Content, action_bar_Activity_Content, action_bar_Activity_Content Set the id of the target ViewGroup to Content. This means that it will become the parent layout of the content hosting area of the AppCompatActivity screen

1.2 Public apis

In addition to the difference between setContentView() in creating a layout structure, AppCompatActivity provides some apis for developers to use that activities don’t.

  • GetSupportActionBar () is used to obtain the ActionBar component specific to AppCompat for developers to customize the ActionBar

  • GetDelegate () gets an instance of the housekeeper AppCompatDelegate implemented inside AppCompatActivity (which will actually get an instance implementing the AppCompatDelegateImpl class via the static create()).

  • GetDrawerToggleDelegate drawer () to obtain the navigation layout DrawerLayout proxy class ActionBarDrawableToggleImpl instance, for the and ActionBar for UI interaction

  • OnNightModeChanged () is different from receiving notification of theme changes only when uiMode is configured for external configuration changes. This API can be called back when the adaptation mode of dark themes (such as follow system setting mode, follow battery setting mode, etc.) has changed

1.3 Attention in use

The AppCompatActivity comment says as follows, and Theme.AppCompat is recommended.

You can add an ActionBar to your activity when running on API level 7 or higher by extending this class for your activity and setting the activity theme to Theme.AppCompat or a similar theme.

After verification, if we use another theme, we get the following crash.

You need to use a Theme.AppCompat theme (or descendant) with this activity.

The idea is that its own caretaker, AppCompatDelegate, intentionally makes sure the Activity uses the AppCompatTheme when creating the ActionBar layout, This is especially true if appCompat-defined windowActionBar attributes are not specified.

// AppCompatThemeImpl.java
	private ViewGroup createSubDecor(a) {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if(! a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { a.recycle();throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity."); }... }Copy the code

As to why exceptions are used to ensure that AppCompatTheme is used, subsequent processing is closely related to AppCompatTheme and many of the subsequent processing will fail if not used.

2. AppCompatDialog

In addition to using a very high AppCompatActivity, AppCompatDialog has no low exposure. Its implementation principle is the same as the AppCompatActivity project, which relies on the caretaker AppCompatDelegate. The same is to extend support for new toolbars and dark themes on top of Dialog.

3. AppCompatTheme

The previously mentioned AppCompatTheme is divided into two main topics.

  • Theme.AppCompat

AppCompat extends the base.v7.theme.AppCompat Theme, specifying AppCompatViewInflater as a parsing class for widgets and other classes, and setting the Base properties defined by AppCompatTheme, whose top-level Theme is still the older Theme, Theme.Holo

  • Theme.AppCompat.DayNight

Can automatically adapt dark theme. It inherits from the Base. V7. Theme. AppCompat. Light, with the Theme. The AppCompat difference lies mainly in its default USES the Theme of Light department, such as the use primary_material_light colorPrimary, Whereas Theme.AppCompat uses the primary_material_dark color

If the App uses this theme, it can automatically adapt the dark mode. How does this work?

4. Dark Theme

AppCompatActivity, when bound to BaseContext, will parse the dark theme mode set by the App through AppCompatDelegate’s applyDayNight() and do some configuration work accordingly.

For example, the commonly used follow power saving mode, which refers to the power saving mode of the device will automatically enter the dark theme, reduce power consumption. If not, return to the day theme.

The implementation is that the AppCompatDelegate will register the broadcast to listen for changes in the power-saving mode (ACTION_POWER_SAVE_MODE_CHANGED). When power saving mode is on/off, the broadcast receiver automatically calls back to updateForNightMode() to update the corresponding topic.

    private boolean applyDayNight(final boolean allowRecreation) {...@NightMode final int nightMode = calculateNightMode();
        @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
        final booleanapplied = updateForNightMode(modeToApply, allowRecreation); .if (nightMode == MODE_NIGHT_AUTO_BATTERY) {
            // Register a broadcast receiver that listens for power saving modegetAutoBatteryNightModeManager().setup(); }... }abstract class AutoNightModeManager {...void setup(a) {...if (mReceiver == null) {
                mReceiver = new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        // Callback after the power saving mode changesonChange(); }}; } mContext.registerReceiver(mReceiver, filter); }... }private class AutoBatteryNightModeManager extends AutoNightModeManager {...@Override
        public void onChange(a) {
            // After the power saving mode changes, call back to the theme switching method to update the theme
            applyDayNight();
        }

        @Override
        IntentFilter createIntentFilterForBroadcastReceiver(a) {
            if (Build.VERSION.SDK_INT >= 21) {
                IntentFilter filter = new IntentFilter();
                filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
                return filter;
            }
            return null; }}Copy the code

The processing of the update topic is the following key code.

    private boolean updateForNightMode(final int mode, final boolean allowRecreation) {...// If the Activity's BaseContext has not been initialized, then the new theme value is directly adapted
        if((sAlwaysOverrideConfiguration || newNightMode ! = applicationNightMode) && ! mBaseContextAttached ...) {...try{... ((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf); handled =true; . }}final int currentNightMode = mContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_MASK;

        // If the Activity's BaseContext is already created,
        // The Activity will be redrawn if the App does not declare that it wants to handle dark theme changes
        if(! handled ...) { ActivityCompat.recreate((Activity) mHost); handled =true;
        }

        // If the App is declared to handle dark theme changes,
        // Then update the new theme value to the Configuration uiMode property
        // and calls back to Activity#onConfigurationChanged() to wait for the App to process itself
        if(! handled && currentNightMode ! = newNightMode) { ... updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode); handled =true;
        }

        // Finally check to notify the App that the Dark Theme mode has changed
        // (Note that this refers to the change in the policy of the dark theme switch in the App Settings,
        // Change from follow system Settings to fixed dark mode, etc.)
        if (handled && mHost instanceofAppCompatActivity) { ((AppCompatActivity) mHost).onNightModeChanged(mode); }... }Copy the code

Careful developers might notice that controls we normally use in the AppCompatActivity layout end up with class names prefixed by AppCompat. For example, you declare a TextView control and you end up with an instance of AppCompatTextView. How did this work? Why did it work? This cannot be compatvieWinflater’s silent contribution.

5. AppCompatViewInflater

The core function is to switch controls in the layout to the AppCompat version. During the phase when LayoutInflater is called to parse an App layout, the caretaker AppCompatDelegate will call AppCompatViewInflater to replace controls one by one in the layout.

    final View createView(View parent, final String name, @NonNull Context context...) {...switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break; . }...return view;
    }
	
	protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }
Copy the code

In addition to the AppCompatTextView mentioned above, the Widgets directory of AppCompat has many controls that are extended to be compatible with new features. Take a look at AppCompatTextView and another commonly used AppCompatImageView.

6. AppCompatTextView

From the code comments you can see that the control on the basis of TextView to add Dynamic Tint and Auto Size two major features.

Let’s take a look at the general effects of these two features.

You can see that the second TextView has darker green on the background and white on the icon, making the icon and text inside it look clearer than the first TextView. This is achieved through the backgroundTint and drawableTint properties provided by AppCompatTextView. The Dynamic Tint feature is the Dynamic Tint feature that dynamically colors the background and icon.

In addition, you can see that the text content of the bottom TextView exactly covers the entire screen without any ellipsis at the end, while the top TextView has a larger font size and is represented by an ellipsis at the end. This auto-sizing effect also relies on the related properties provided by AppCompatTextView. This is the Auto Size feature.

6.1 the Dynamic Tint

Mainly rely on AppCompatBackgroundHelper and AppCompatDrawableManager implementation, including reflect the static configuration and dynamic modification of Tint properties.

The main experience of these steps:

  1. LoadFromAttributes () resolves the Tint property configured in the layout. The core processing is the ability to resolve the Tint resource set into a ColorStateList instance.
// ColorStateListInflaterCompat.java
    private static ColorStateList inflate(Resources r, XmlPullParser parser) {...while((type = parser.next()) ! = XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type ! = XmlPullParser.END_TAG)) { ...final intcolor = modulateColorAlpha(baseColor, alphaMod); colorList = GrowingArrayUtils.append(colorList, listSize, color); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; }...return new ColorStateList(stateSpecs, colors);
    }
Copy the code
  1. SetInternalBackgroundTint applySupportBackgroundTint and () () is responsible for the management and to distinguish the Tint color from static configuration properties and external dynamic configuration parameters

  2. TintDrawable () takes care of the coloring, essentially calling Drawable#setColorFilter() to refresh the drawing of the color

// ResourceManagerInternal.java
    static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {...if (tint.mHasTintList || tint.mHasTintMode) {
            drawable.setColorFilter(createTintFilter(
                    tint.mHasTintList ? tint.mTintList : null,
                    tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,
                    state));
        } else{ drawable.clearColorFilter(); }... }Copy the code

6.2 Auto Size

To solve the problem is that the Text content according to the maximum width and font size, the best size calculation of adaptive dependence AppCompatTextHelper and AppCompatTextViewAutoSizeHelper implementation.

  1. Parse the AutoSize attribute configuration and set the Flag of whether auto-adaptation is required.
// AppCompatTextViewAutoSizeHelper.java
	void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {...if(a.hasValue(R.styleable.AppCompatTextView_autoSizeTextType)) { mAutoSizeTextType = a.getInt(R.styleable.AppCompatTextView_autoSizeTextType, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE); }...if (supportsAutoSizeText()) {
            if(mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) { ... setupAutoSizeText(); }... }}private boolean setupAutoSizeText(a) {
        if (supportsAutoSizeText()
                && mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) {
            ...
            if(! mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length ==0) {...for (int i = 0; i < autoSizeValuesLength; i++) {
                    autoSizeTextSizesInPx[i] = Math.round(
                            mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
                }
                mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
            }
            mNeedsAutoSizeText = true; }... }Copy the code
  1. Calculate the appropriate font size as the text content initializes or changes and reflect it on the UI.
// AppCompatTextView.java
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {...if(mTextHelper ! =null&&! PLATFORM_SUPPORTS_AUTOSIZE && mTextHelper.isAutoSizeEnabled()) { mTextHelper.autoSizeText(); }}// AppCompatTextHelper.java
    void autoSizeText(a) {
        mAutoSizeTextHelper.autoSizeText();
    }

// AppCompatTextViewAutoSizeHelper.java
	void autoSizeText(a) {...if (mNeedsAutoSizeText) {
            ...
            synchronized (TEMP_RECTF) {
                ...
                // Calculate the optimal size
                final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
                // Update the size if it does not match the preset size
                if(optimalTextSize ! = mTextView.getTextSize()) { setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize); }}}... }Copy the code

7. AppCompatImageView

Extended Dynamic Tint for background and SRC as AppCompatTextView.

Unlike AppCompatTextView, the attribute used by AppCompatImageView for icon coloring is not ATTR# drawableTint is ATTR# tint. By AppCompatImageHelper and ImageViewCompat class implementation, the principle of much the same, no longer repeated.

8. Auxiliary class

Developers of the AppCompat framework use many helper classes to implement features such as AppCompat extension controls. You can explore the details and learn some clever implementation ideas.

  • AppCompatBackgroundHelper
  • AppCompatDrawableManager
  • AppCompatTextHelper
  • AppCompatTextViewAutoSizeHelper
  • AppCompatTextClassifierHelper
  • AppCompatResources
  • AppCompatImageHelper

.

9. The class diagram

Finally, check out the simple class diagram of the AppCompat framework to help you get an overview.

10. Conclusion

As you can see, the AppCompat framework is relatively simple overall, and therefore easy to overlook. But as a cornerstone of the Jetpack series, it’s important to know.