AppCompat发布两年了,还没了解?

article/2025/10/18 19:31:31

近日随笔
近期疫情日渐严峻,大家多多保重,出门记得戴口罩。希望河北,黑龙江能尽早控制住好局面迎来拐点,全国人民过个好年。

在这里插入图片描述

为了能够让低版本的Android系统能够运行新特性,AppCompat框架自Support时代就已推出。但随着AndroidX的一统江湖,AppCompat的相关类则一并迁移到了AndroidX库里。

Android开发者应该都不陌生,在Android Studio上创建的项目默认采用AppCompatActivity作为Activity的基类。可以说,这个类是整个AppCompat框架里最重要的类,也是我们今天研究AppCompat的起点。

AppCompatActivity

其间接继承自Activity,之间还继承了其他Activity特色类,可以使得低版本上运行的Activity也能拥有ToolBar和暗黑主题等新功能。

AppCompatActivity extends FragmentActivity extends ComponentActivity extends ComponentActivity extends Activity*

  • FragmentActivity
    采用FragmentController类对AndroidX的Fragment新组件提供支撑,比如提供了咱们常用的getSupportFragmentManager() API。

  • androidx.activity.ComponentActivity
    实现了ViewModel接口,和Lifecycle框架进行配合以支撑ViewModel框架的运行。

  • androidx.core.app.ComponentActivity
    实现了Lifecycle接口并通过ReportFragment支撑Lifecycle框架的运行。

先来感受一下AppCompatActivity和Activity在UI上的表现。

在这里插入图片描述

从对比图上看并没有太大区别,但从UI的树形图上看是有些区别的。比如AppCompatActivity的content区域的上方多了一个LinearLayout和ViewStub控件,再比如AppCompatActivity下面的是AppCompatTextView而不是TextView。

那这些差异是如何实现的,有什么用意?

谈到AppCompatActivity实现的话不得不提幕后的大管家AppCompatDelegate类,其承载了AppCompatActivity几乎所有的实现工作。比如AppCompatActivity复写了setContentView()的逻辑,交由大管家AppCompatDelegate去实现其特有的UI结构。

AppCompatDelegate

重点介绍下大管家的头号工作setContentView(),具体分为如下几个小任务。

  1. ensureSubDecor() 确保ActionBar的特有UI结构创建完毕

  2. removeAllViews() 确保ContentView的所有Child全部被移除干净

  3. inflate() 将画面的内容布局解析并添加到ContentView

第一步ensureSubDecor()的内容比较多,又分为几个子任务,包括调用createSubDecor()创建ActionBar特有布局,setWindowTitle()将Activity标题反映到ToolBar上以及applyFixedSizeWindow()去调整DecorView尺寸。

核心内容在于createSubDecor()这个子任务。它需要确保ActionBar的特有布局创建出来并和Window的DecorView产生联系。

  1. ensureWindow()
    获取Activity所属的Window引用并添加window相关回调

  2. getDecorView()
    告知Window去创建DecorView,这里要提一下PhoneWindow的generateLayout(),其将依据主题的创建不同的布局结构,比如AppCompatActivity的话将解析screen_simple.xml得到DecorView的基本结构,其包括根布局LinearLayout,用来映射actionmode布局的viewstub以及承载App内容的id为ContentView

  3. inflate()
    获取ActionBar的布局,主要是abc_screen_toolbar.xml和abc_screen_content_include.xml两个文件

  4. removeViewAt()和addView()
    ContentView的子View迁移至ActionBar布局下。具体方法是将其所有child移除并add到ActionBar布局下id为action_bar_activity_content的ViewGroup下面,并将原有ContentView的id置空,同时将该目标ViewGroup的id设置为Content。意味着它将成为AppCompatActivity画面承载内容区域的父布局

公开的API

除了setContentView()在打造布局结构上的差异,AppCompatActivity还提供了些Activity所没有的API供开发者使用。

  • getSupportActionBar() 用以获取AppCompat特有的ActionBar组件供开发者定制ActionBar

  • getDelegate() 获取AppCompatActivity内部实现的大管家AppCompatDelegate的实例(实际上将通过静态的create()获取实现类AppCompatDelegateImpl的实例)

  • getDrawerToggleDelegate() 获取抽屉导航布局DrawerLayout的代理类ActionBarDrawableToggleImpl的实例,用来和ActionBar进行UI的交互

  • onNightModeChanged() 不同于配置了uiMode的外部配置变更后才能收到主题变化的通知,本API可以在暗黑主题的适配模式(比如跟随系统设置模式和跟随电量设置模式等)发生变化后得到回调,可利用这个时机做些补充处理

使用上的注意

AppCompatActivity的注释上有如下说明,推荐采用Theme.AppCompat主题。

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.

经过验证如果我们使用了别的主题就会得到如下的crash。

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

原理在于上面自己的大管家AppCompatDelegate在创建ActionBar布局的时候有意地确保Activity是否采用了AppCompatTheme主题,尤其是如果没有指定AppCompat定义的windowActionBar的属性的话,将抛出如上的异常。

// AppCompatThemeImpl.javaprivate ViewGroup createSubDecor() {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.");}...}

至于为什么用异常来确保AppCompatTheme的采用,因为后续的处理跟AppCompatTheme息息相关,如果没有采用后面的很多处理将失效。

AppCompatDialog

除了使用极高的AppCompatActivity以外,AppCompatDialog的曝光率也不低。其实现原理和AppCompatActivity企划一致,都是依赖大管家AppCompatDelegate进行实现。一样是为了在Dialog的基础上扩展出新ToolBar和暗黑主题的支持。

AppCompatTheme

前面提到的AppCompatTheme主要分为两个主题。

  • Theme.AppCompat
    继承自Base.V7.Theme.AppCompat主题,指定AppCompatViewInflater为widget等class的解析类,并设置AppCompatTheme所定义的基本属性,其顶级主题仍旧是老牌的主题Theme.Holo

  • Theme.AppCompat.DayNight
    能够自动适配暗黑主题。其继承自Base.V7.Theme.AppCompat.Light,与Theme.AppCompat的区别主要在于其默认情况下采用了light系的主题,比如colorPrimary采用primary_material_light,而Theme.AppCompat则采用primary_material_dark颜色

App采用了该主题就可以自动适配暗黑模式,这是如何做到的?

Dark Theme 暗黑模式

AppCompatActivity在绑定BaseContext的时候会通过AppCompatDelegate的applyDayNight()去解析App设置的暗黑主题模式并做出一些相应的配置工作。

比如常用的跟随省电模式,其指的是设备的省电模式开启后将自动进入暗黑主题,降低功耗。反之关闭之后返回到白天主题。

具体实现是AppCompatDelegate将注册监听省电模式变化的广播(ACTION_POWER_SAVE_MODE_CHANGED)。当省电模式开启/关闭时,广播接收器将自动回调updateForNightMode()去更新对应的主题。

    private boolean applyDayNight(final boolean allowRecreation) {...@NightMode final int nightMode = calculateNightMode();@ApplyableNightMode final int modeToApply = mapNightMode(nightMode);final boolean applied = updateForNightMode(modeToApply, allowRecreation);...if (nightMode == MODE_NIGHT_AUTO_BATTERY) {// 注册监听省电模式的广播接收器getAutoBatteryNightModeManager().setup();}...}abstract class AutoNightModeManager {...void setup() {...if (mReceiver == null) {mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 省电模式变化后的回调onChange();}};}mContext.registerReceiver(mReceiver, filter);}...}private class AutoBatteryNightModeManager extends AutoNightModeManager {...@Overridepublic void onChange() {// 省电模式变化后回调主题切换方法更新主题applyDayNight();}@OverrideIntentFilter createIntentFilterForBroadcastReceiver() {if (Build.VERSION.SDK_INT >= 21) {IntentFilter filter = new IntentFilter();filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);return filter;}return null;}}

更新主题的处理则是如下关键代码。

    private boolean updateForNightMode(final int mode, final boolean allowRecreation) {...// 如果Activity的BaseContext尚未初始化则直接适配新的主题值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;// 如果Activity的BaseContext已经创建,// 且App没有声明要处理暗黑主题变化的话,将重绘Activityif (!handled...) {ActivityCompat.recreate((Activity) mHost);handled = true;}// 假使App声明了处理暗黑主题变化的话,// 那么将新的主题值更新到Configuration的uiMode属性// 并回调Activity#onConfigurationChanged(),等待App的自行处理if (!handled && currentNightMode != newNightMode) {...updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode);handled = true;}// 最后检查是否要通知App暗黑主题模式发生变化// (注意这里指的是App设置的暗黑主题切换的策略发生变更,// 比如由跟随系统设置变更为固定暗黑模式等)if (handled && mHost instanceof AppCompatActivity) {((AppCompatActivity) mHost).onNightModeChanged(mode);}...}

细心的开发者可能会注意到我们平常在AppCompatActivity的布局里使用的控件,最终得到的类名称里会多上AppCompat的前缀。比如声明的是TextView控件最后得到的是AppCompatTextView类的实例。这是怎么做到的,为什么这么做?这就离不开AppCompatViewInflater的默默付出。

AppCompatViewInflater

核心功能就是将布局里的控件切换为AppCompat版本。在调用LayoutInflater解析App布局的阶段,大管家AppCompatDelegate将调用AppCompatViewInflater将布局中的控件逐个替换。

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

除了上面提到的AppCompatTextView,AppCompat的widget目录下有很多为了兼容新特性扩展的控件。以AppCompatTextView和另一个常用的AppCompatImageView来一探究竟。

AppCompatTextView

由代码注释就可以看出来该控件在TextView的基础上增加了Dynamic TintAuto Size两大特性。

先看下这两特性大体是什么效果。

在这里插入图片描述

可以看到第二个TextView对背景着上了更深的绿色,并对icon着上了白色,使得它内部的icon和文字相较第一个TextView看起来更清楚。这是通过AppCompatTextView提供的backgroundTint和drawableTint属性实现的,这种给背景和icon动态着色的功能就是Dynamic Tint特性。

另外可以看到最下面TextView的文本内容正好铺满整个屏幕没有在末尾出现省略,而上面那个TextView的字体尺寸较大且在尾部用省略号表示。这种自动适配字体尺寸的效果同样是依赖AppCompatTextView提供的相关属性来完成。此为Auto Size特性。

Dynamic Tint

主要依赖AppCompatBackgroundHelper和AppCompatDrawableManager实现,包括反映静态配置和动态修改的Tint属性。

主要经历这几步:

  1. loadFromAttributes() 解析布局里配置的Tint属性,核心处理在于能够将设置的Tint资源解析成ColorStateList实例。
// ColorStateListInflaterCompat.javaprivate static ColorStateList inflate(Resources r, XmlPullParser parser) {...while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {...final int color = modulateColorAlpha(baseColor, alphaMod);colorList = GrowingArrayUtils.append(colorList, listSize, color);stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);listSize++;}...return new ColorStateList(stateSpecs, colors);}
  1. setInternalBackgroundTint()和applySupportBackgroundTint() 负责管理和区分Tint颜色的取自静态配置的属性还是外部动态配置的参数

  2. tintDrawable()负责着色,本质在于调用Drawable#setColorFilter()去刷新颜色的绘制

// ResourceManagerInternal.javastatic 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();}...}

Auto Size

需要解决的问题是对Text内容依据最大宽度和当前size计算自适应的最佳字体尺寸,依赖AppCompatTextHelper和AppCompatTextViewAutoSizeHelper实现。

  1. 解析AutoSize相关属性的配置并设定是否需要自动适配字体尺寸的Flag。
// AppCompatTextViewAutoSizeHelper.javavoid 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() {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;}...}
  1. 在文本内容初始化或变化的时候计算合适的字体尺寸并反映到UI上。
// AppCompatTextView.javaprotected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {...if (mTextHelper != null && !PLATFORM_SUPPORTS_AUTOSIZE && mTextHelper.isAutoSizeEnabled()) {mTextHelper.autoSizeText();}}// AppCompatTextHelper.javavoid autoSizeText() {mAutoSizeTextHelper.autoSizeText();}// AppCompatTextViewAutoSizeHelper.javavoid autoSizeText() {...if (mNeedsAutoSizeText) {...synchronized (TEMP_RECTF) {...// 计算最佳sizefinal float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);// 如果和预设的size不一致的话更新sizeif (optimalTextSize != mTextView.getTextSize()) {setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize);}}}...}

AppCompatImageView

AppCompatTextView一样扩展了针对background和src的Dynamic Tint功能。

在这里插入图片描述

AppCompatTextView不同的是AppCompatImageView对icon着色采用的属性不是*attr#drawableTintattr#tint***。由AppCompatImageHelperImageViewCompat类实现,原理大同小异,不再赘述。

辅助类

AppCompat框架的开发人员在实现AppCompat扩展控件等特性的时候用到很多辅助类,大家可以自行研究下其细节,学习下一些巧妙的实现思路。

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

类图

最后上一下AppCompat框架的简易类图,帮助大家有个整体上的认识。

在这里插入图片描述

总结

可以看到AppCompat框架整体比较简单,因此也容易被大家忽视。但作为Jetpack系列里的入口,了解一下很有必要。

原创不易,如果本篇文章引起你的思考和兴趣,欢迎点赞,收藏和关注。如果想要了解更多更全信息,扫码关注博主公众号TechMerger

在这里插入图片描述


http://chatgpt.dhexx.cn/article/DIqhEies.shtml

相关文章

AppCompat (AppCompatActivity) Jetpack

进入AppCompat章节后&#xff0c;我们发现它又被分为了4个部分&#xff0c;这4个部分被称为“key class”&#xff0c;也就是重点类&#xff0c;它们分别是&#xff1a; ActionBar&#xff1a;提供Actionbar用户界面模式的实现&#xff1b;AppCompatActivity&#xff1a;添加可…

【5】SpringBoot日志存储路径和设置日志格式

SpringBoot日志存储路径和设置日志格式 文章目录 SpringBoot日志存储路径和设置日志格式01、分析02、解决方案03、Springboot的日志的解决方案&#xff08;掌握&#xff09;04、查看springboot的日志的整个体系05、slf4j、logback和log4j三者的关系06、springboot的日志搭配07、…

nginx日志格式分析及修改

修改nginx日志打印格式 一. 打开终端&#xff0c;登录服务器并输入服务器密码 //ssh 用户名服务器ip ssh root192.168.0.132二. 切换到nginx目录 cd /var/log/nginx/三. 查看nginx日志 tail -f access.log日志说明&#xff1a; //默认的nginx标准日志格式 192.168.10.251 …

Nginx配置-日志格式配置

Nginx配置-日志格式配置 一、默认的日志格式二、我使用的日志格式三、参数四、测试效果 五一上线了一个小的预约程序&#xff0c;配置通过Nginx进行访问入口&#xff0c;默认的日志是没有请求时间的&#xff0c;因此需要配置一下&#xff0c;将每一次的请求的访问响应时间记录出…

Apache日志记录格式-LogFormat配置详解

Apache日志记录格式-LogFormat配置详解 前言 定制日志文件的格式涉及到三个指令&#xff0c;即LogFormat指令和CustomLog指令和ErrorLog指令&#xff0c;默认httpd.conf文件提供了关于这两个指令的几个示例。 格式设置 LogFormat LogFormat指令定义格式并为格式指定一个名…

springboot项目中日志使用----自定义日志格式(可直接使用)

springboot项目中日志使用 1、为什么加日志 1.1 日志是什么&#xff1f; 日志文件提供精确的系统记录&#xff0c;根据日志最终定位到错误详情和根源。日志的特点是&#xff0c;它描述一些离散的&#xff08;不连续的&#xff09;事件。例如&#xff1a;应用通过一个滚动的文…

python设置日志格式

# %(asctime)s 字符串形式的当前时间。 # %(levelname)s 文本形式的日志级别 # %(name)s Logger的名字 # %(filename)s 调用日志输出函数的模块的文件名 # %(funcName)s 调用日志输出函数的函数名 # %(lineno)d 调用日志输出函数的语句所在的代码行 # %(message)…

Log4j日志输出格式

Log4j日志输出格式 对于我才开始工作来说&#xff0c;输出日志唯一的目的就是打印代码的调试信息&#xff0c;当有项目运行什么问题或者错误可以通过查看日志快速的找到问题根源&#xff08;这也是我来公司最开始就遇到的问题&#xff09;。 先分享一个公司框架自带的通用日志…

logback 日志输出格式

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainai.net/zhanghan​ 【前言】 日志对一个系统的重要性不言而喻&#xff1b;日志通常是在排查问题时给人看&#xff0c;一个友好的输出样式让人看到后赏心悦目&#xff0c;排查效率通常也会随之提高&#xff1b;…

Nginx的日志格式

vim /etc/logstash/conf.d/ filter模块的作用&#xff1a;过滤&#xff0c;{在一个大的真空管里面&#xff0c;中间有一个过滤网&#xff0c;只有比这个小的东西能过去&#xff0c;大的会拦住} 在grok中使用match进行选择&#xff0c;所传送过来的是下面这样。希望把他们变成i…

什么是日志文件

文章目录 什么是日志文件Centos 7 日志文件简易说明日志文件的重要性Linux常见的日志文件文件名/var/log/boot.log/var/log/cron/var/log/dmesg/var/log/lastlog/var/log/maillog或 /var/log/mail/*/var/log/messages/var/log/secure/var/log/wtmp、/var/log/faillog/var/log/h…

AE2018插件AfterCodecs v1.9.0安装教程,(AE mp4)

1.自己百度下载好 2.这2个文件夹要复制&#xff0c;其他可以不管 复制Autokroma AfterCodecs 文件夹到 C:\Program Files\Adobe\Common\Plug-ins\7.0\MediaCore 复制com.autokroma.afcpanel 文件夹到 Adobe After Effects CC 2018\Support Files\Plug-ins\Extensions&#x…

PS/PR/AE全套插件一键安装包无需注册码

注意事项&#xff1a; 1、下载前请关闭360、电脑管家等杀毒软件&#xff0c;否则会误报病毒导致无法安装&#xff1b; 2、安装PS/PR/AE插件合集前&#xff0c;请务必先安装好Adobe软件&#xff1b; 3、安装PS/PR/AE插件合集前&#xff0c;请务必关闭Adobe旗下所有软件。 安装步…

Ae 2020最新版下载地址 一键安装Windows

各类cc软件。 地址&#xff1a;https://www.yuque.com/docs/share/cf424323-49e5-43e6-a80c-4f07ce467d56 访问密码&#xff1a;关注公众号"糊糊省钱"&#xff0c;回复“ps”获取

Adobe After Effects(AE)2022软件安装[MAC]

Adobe After Effects简称“AE”&#xff0c;Adobe After Effects 2022mac是Adobe公司推出的一款图形视频处理软件&#xff0c;适用于从事设计和视频特技的机构&#xff0c;包括电视台、动画制作公司、个人后期制作工作室以及多媒体工作室&#xff0c;属于层类型后期软件。Adobe…

AE CC2018/64位安装步骤及各种出现问题!!(真的装了好久)

最近爱上录vlog&#xff0c;但是作为一个计算机学院的学生我怎么仅仅局限于手机里的剪视频软件呢&#xff01;然后我打算学一下AE&#xff0c;但是在这个py的过程中&#xff0c;真的废了快一个晚上了&#xff0c;从第一次安装出现问题我就应该立马百度&#xff0c;但是我没有&a…

2023AE软件、Adobe After Effects安装下载教程

2023AE软件是一款由Adobe公司开发的视频编辑软件&#xff0c;也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用&#xff0c;用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式&#xff0c;具有丰富的插件和预设&#…

Adobe After Effect (AE) cc2020 安装教程【64位】

Adobe After Effects简称“AE” 是Adobe公司推出的一款图形视频处理软件&#xff0c;适用于从事设计和视频特技的机构&#xff0c;包括电视台、动画制作公司、个人后期制作工作室以及多媒体工作室。属于层类型后期软件。 ----介绍来源于百度 【软件名称】&#xff1a; After …

【AE2019】Adobe_After_Effects_2019软件下载及安装教程

下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1-foCrqbfKugK6cf7M4nevQ 提取码&#xff1a;6ewm 下载方式&#xff1a; 复制链接到浏览器中打开&#xff0c;输入提取码&#xff0c;保存到网盘&#xff0c;然后打开百度网盘&#xff08;PC端&#xff09;&a…

AE中文版 AE2021中文版下载 AfterEffects2021中文版ACR14.0最新下载和安装

AE我目前最为专业也是最火的特效制作工具&#xff0c;全称叫AfterEffects&#xff0c;现在最新的就是2021的&#xff0c;而且是中文&#xff0c;并且内置的ACR已更新到最新的14.0&#xff0c;非常适合新手朋友刚开始零基础入门学习使用&#xff0c;并且非常好上手&#xff0c;学…