简介:面向Android开发者的兼容性支持库合集,聚焦v4与v7 support两个经典jar包,适合需要维护旧版本适配、理解Android向后兼容策略的初中级开发者参考。压缩包共447个文件,以223个xml资源定义与213个png图片素材为主,另含2个jar核心库、3个java源码及readme、properties等配置说明,整体约2.05MB,目录结构保留了appcompat工程的原始组织方式。已有241人浏览学习。资源完整呈现了support-v4对Fragment、Loader等基础组件的低版本扩展,以及support-v7-appcompat对ActionBar、Material Design主题与AppCompatActivity的兼容实现,读者可据此理清两库的职责边界、资源引用关系与工程集成方式,为迁移至AndroidX或排查旧项目兼容问题提供直观参照。
1. 两个 jar 包,为什么老项目一升级就翻车
接手一个五年前的 Android 工程,Gradle 一同步就报Duplicate class android.support.v4.app.NotificationCompat,或者resources.arsc冲突、appcompat主题找不到父样式,这类问题十有八九绕不开android-support-v4.jar和android-support-v7-appcompat.jar这对老搭档。它们不是普通依赖,而是 Android 兼容库体系里两个定位完全不同的包:v4 提供Fragment、ViewPager、NotificationCompat、Loader等向后兼容的 API 实现,v7-appcompat 则把ActionBar、AppCompatActivity、主题与资源打包成一套可用的 UI 兼容层。搞不清两者的依赖关系和资源合并规则,升级、换 AndroidX、多模块拆分都会踩坑。这篇笔记面向还在维护老工程、需要把这两个 jar 用对或用干净的一线开发者,从依赖结构讲到实际配置、冲突排查和迁移判断,能照着复现。
2. 拆开看依赖:v4 与 v7-appcompat 到底谁依赖谁
2.1 两个包的职责边界与依赖方向
先把关系说清楚,后面所有冲突都源于这里。android-support-v4.jar是纯 Java 库,早期以单个 jar 形式分发,包含android.support.v4.*下的兼容类,不携带资源文件(后来拆成 v4 的多个子模块如support-fragment、support-core-utils才带资源)。android-support-v7-appcompat.jar是库工程(library project),除了 jar 里的android.support.v7.app.*类,还带res/目录下的主题、样式、颜色、布局资源,并且依赖 v4——AppCompatActivity内部用到FragmentActivity,ActionBar实现依赖 v4 的ViewPager、Loader等。
所以依赖方向是单向的:v7-appcompat → v4。工程里同时引入两者时,v4 不能重复放两份,否则就是Duplicate class的直接来源。常见做法是只显式声明 v7-appcompat,让构建系统把 v4 作为传递依赖带进来;如果确实需要单独用 v4 的新 API,再显式加 v4,但要保证版本对齐。
| 包 | 类型 | 是否带资源 | 依赖关系 | 典型类 |
|---|---|---|---|---|
| android-support-v4 | Java 库(jar) | 早期不带,拆分后子模块带 | 无 | Fragment、ViewPager、NotificationCompat |
| android-support-v7-appcompat | 库工程(jar + res) | 带 | 依赖 v4 | AppCompatActivity、ActionBar、Theme.AppCompat |
提示:判断一个工程用的是「单 jar 老式引入」还是「Gradle 坐标引入」,看
libs/目录下有没有裸 jar、build.gradle里是compile files(...)还是compile 'com.android.support:appcompat-v7:x.x.x'。两种方式的冲突表现不一样。
2.2 老式 jar 引入与 Gradle 坐标引入的差异
老工程常见两种引入姿势,处理方式完全不同。
第一种是 Eclipse 时代遗留:libs/android-support-v4.jar和把 v7-appcompat 作为 library project 引用。这种结构下,v4 的 jar 可能同时存在于主工程libs/和 v7-appcompat 库工程的libs/里,构建时两份都会被 dex,直接Duplicate class。解决方式是删掉其中一份,通常保留库工程里的,主工程不再放 v4。
第二种是 Gradle 坐标引入:
// 只声明 appcompat,v4 作为传递依赖自动带入 dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // 需要 v4 的独立子模块时再显式加,版本必须与 appcompat 一致 implementation 'com.android.support:support-v4:28.0.0' }这里的关键参数是版本号:appcompat-v7和support-v4必须同一版本,混用 27 和 28 会出现NoSuchMethodError或资源 ID 错位。implementation与老compile的区别在于传递性可见范围,多模块工程里用implementation能减少 v4 被意外暴露到上层模块导致的重复。
2.3 用依赖树确认实际引入的版本
不要靠猜。Gradle 工程直接跑依赖树,看 v4 是从哪条路径进来的、有没有多版本共存:
# 查看 appcompat 及其传递依赖,过滤 support 相关 ./gradlew :app:dependencies --configuration debugCompileClasspath | grep -i support输出里如果出现com.android.support:support-v4:28.0.0 -> 27.1.1这种箭头,说明有别的库把 v4 降级了,需要resolutionStrategy强制对齐:
configurations.all { resolutionStrategy { force 'com.android.support:support-v4:28.0.0' force 'com.android.support:appcompat-v7:28.0.0' } }force的作用是覆盖所有传递依赖声明的版本,参数写目标版本全称。这一步做完再同步,Duplicate class和NoSuchMethodError能消掉一大半。老式 jar 工程没有依赖树,只能手动比对libs/和库工程libs/下的文件名与文件大小,同名不同大小基本就是版本不一致。
3. 动手配置:从零把 v4 与 v7-appcompat 接进工程
3.1 最小可用配置与主题继承
先给一份能跑起来的最小配置。Gradle 工程在app/build.gradle里:
android { compileSdkVersion 28 defaultConfig { minSdkVersion 15 targetSdkVersion 28 } } dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' }minSdkVersion 15是 appcompat-v7 28 版本的下限,低于这个值构建会直接报错。主题必须在AndroidManifest.xml的<application>或具体<activity>上指定继承自Theme.AppCompat的样式,否则AppCompatActivity启动时抛IllegalStateException: You need to use a Theme.AppCompat theme:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>colorPrimary、colorPrimaryDark、colorAccent是 appcompat 主题约定的三个颜色属性,缺了不会崩,但 ActionBar 和控件着色会退回默认值,视觉上对不上设计稿。Activity 必须继承AppCompatActivity而不是Activity,否则主题里的 AppCompat 属性不生效。
3.2 老式 jar 工程的目录结构与引用
Eclipse 结构或未迁移的工程,目录大致是这样:
project/ ├── libs/ │ └── (不要放 android-support-v4.jar) ├── appcompat/ # v7-appcompat 库工程 │ ├── libs/ │ │ └── android-support-v4.jar │ └── res/ └── src/主工程project.properties里加android.library.reference.1=../appcompat,主工程libs/下不要再放 v4 的 jar。如果历史原因主工程已经放了,删掉它,重新 clean 构建。库工程的res/会被合并进主工程,所以主工程里不要定义同名资源(比如也建一个values/colors.xml里叫colorPrimary),否则资源合并报Resource entry colorPrimary is already defined。
3.3 资源合并与tools:replace的使用
多模块或引入多个 support 库时,AndroidManifest.xml的application节点属性冲突很常见,典型报错是Attribute application@theme value=... also present at ...。解决方式是在主工程 manifest 里用tools:replace显式声明以谁为准:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:theme="@style/AppTheme" tools:replace="android:theme"> </application> </manifest>tools:replace的参数是要覆盖的属性名,多个用逗号分隔。它只影响 manifest 合并,不影响代码。资源层面的冲突(同名 string、color)不能用tools:replace解决,只能改名或删重复定义。这一步是血泪经验:先看构建日志里also present at后面跟的是哪个模块,再去那个模块删或改,比盲目加tools:replace靠谱。
4. 避坑排查:五类高频翻车现场
4.1 Duplicate class:v4 被放了两份
现象:构建报Duplicate class android.support.v4.app.Fragment found in modules,或者 dex 阶段com.android.dex.DexException。
原因:主工程libs/和 v7-appcompat 库工程libs/各有一份 v4 jar;或 Gradle 里同时显式声明了support-v4和某个内部已带 v4 的库,版本不同导致两份共存。
解决:Gradle 工程跑 2.3 的依赖树,找到重复来源,用force对齐版本或exclude掉多余传递依赖;老式工程删掉主工程libs/下的 v4 jar,只保留库工程里那份,clean 后重建。
4.2 主题不生效:AppCompatActivity启动即崩
现象:java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity。
原因:Activity 继承了AppCompatActivity,但 manifest 里该 Activity 或 application 的android:theme指向的是系统主题(如@android:style/Theme.Material.Light)或自定义但没继承Theme.AppCompat的样式。
解决:把主题父样式改成Theme.AppCompat系列,或在 Activity 节点单独指定android:theme="@style/AppTheme"。注意Theme.AppCompat.NoActionBar与Theme.AppCompat.Light的区别在于是否带 ActionBar,选错不会崩但布局会多一条或空一块。
4.3 资源找不到:resource colorPrimary not found
现象:编译期AAPT: error: resource color/colorPrimary not found,或运行期Resources$NotFoundException。
原因:appcompat 库工程的res/没有被正确合并,常见于老式工程project.properties引用路径写错,或 Gradle 工程把 appcompat 写成了compileOnly导致资源不参与打包。
解决:确认库工程引用路径存在且指向含res/的目录;Gradle 里 appcompat 必须用implementation或api,不能用compileOnly。改完执行./gradlew clean assembleDebug,不要只做增量构建。
4.4 版本错位:NoSuchMethodError与NoClassDefFoundError
现象:运行期调用AppCompatActivity或Fragment的某个方法时抛NoSuchMethodError,或类加载失败。
原因:v4 与 appcompat 版本不一致,比如 appcompat 28 配 v4 27,方法签名在版本间有增删;或force只强制了其中一个包。
解决:两个包版本号必须完全一致,resolutionStrategy里同时force两者。老式 jar 工程比对两个 jar 的文件大小和修改日期,不一致就换成同一版本分发包。
4.5 迁移 AndroidX 时的残留引用
现象:工程已迁 AndroidX,但某个老模块或第三方 aar 仍引用android.support.*,构建报This project uses AndroidX dependencies, but the 'android.useAndroidX' property is not enabled或混合引用错误。
原因:迁移只改了主模块,子模块或依赖库没改;或gradle.properties里android.useAndroidX=true与android.enableJetifier=true没同时开。
解决:gradle.properties两个开关都置 true,enableJetifier负责把第三方库里的 support 引用自动重写为 AndroidX;自己代码里的android.support.v4.*导入要手动替换为androidx.core.*、androidx.fragment.*等对应包,IDE 的 Refactor 能批量做,但ViewPager这类要确认映射到androidx.viewpager.widget.ViewPager而不是新版viewpager2。
5. 判断去留:什么时候该把这两个 jar 换掉
5.1 用一张对照表决定留还是迁
这两个包本身没有「必须升级」的强制节点,但维护成本随工程规模上升。判断依据可以落成一张表:
| 信号 | 建议动作 |
|---|---|
| 工程只剩单模块、minSdk ≤ 21、无新功能开发 | 保留现状,锁死版本,别动 |
| 多模块、依赖第三方 aar 较多 | 迁 AndroidX,开 Jetifier |
| 需要用到新控件或新兼容 API | 迁 AndroidX,support 库已停止更新 |
| 构建频繁报 Duplicate class / 资源冲突 | 先按第 4 章排查,再评估迁移 |
迁移的核心动作是把com.android.support:appcompat-v7换成androidx.appcompat:appcompat,support-v4的各个子模块按官方映射表逐个替换。映射不是一一对应,support-v4被拆成androidx.core、androidx.fragment、androidx.media等多个包,导入语句要按类逐个改,批量替换容易漏。
5.2 迁移后的验证清单
迁移完不要只看编译通过。跑一遍完整构建加关键路径回归:
# 全量构建,确认无 support 残留 ./gradlew clean assembleDebug # 扫描源码里是否还有 android.support 引用 grep -rn "android.support" app/src/ || echo "no support refs"grep那行是后悔药:编译能过不代表运行期没问题,有些反射调用或字符串拼接的类名不会在编译期暴露。再手动走一遍带Fragment、ViewPager、ActionBar的页面,确认主题、菜单、返回栈行为正常。我一般会在迁移分支上单独跑一次lint,把SupportAnnotationUsage、RestrictedApi这类警告清掉再合并。
从那以后我每次碰老工程,第一步都是先跑依赖树和grep android.support,把 v4 和 v7-appcompat 的实际引入路径摸清楚再动手,而不是看到报错就加force。这套习惯帮我省掉了大量「改一处崩三处」的返工。希望帮到你。
本文还有配套的精品资源,点击获取