App瘦身优化实战

我们之前分析过瘦身优化:App瘦身优化

这篇文章我们继续分析瘦身优化

“每个字节都应该为用户体验服务,冗余即是对用户的不敬”

一、整体思路 & 优化流程

目标明确
明确一个目标:例如 安装包 ≤ 50MB 或 ​*相比当前减少 30%*​。区分:​安装包体积(APK/​​​AAB​**)** vs ​安装后占用空间​,手段有所不同。 量化分析
使用工具:

Analyze APK
(Android Studio 内置):看各目录和 .dex 的体积占比。
apktool
/
bundletool
解包进一步分析。 先找“大头”:一般是

lib/
(so库)
res/
(图片、音视频、字体)
classes*.dex
(代码)assets(离线包、H5、模型文件等) 优先级
P0:资源 & 多余依赖​(通常最容易减 20~40%)P1:代码混淆压缩 & 多dex瘦身​P2:架构升级(​​​Android App Bundle​**、动态特性模块拆分)**P3:深度方案(重构架构、替换大模块)

二、基础配置层面(低成本高收益)

使用 Android App Bundle(AAB)

如果还在发 APK,建议改为 AAB​​​ + Google Play/各应用市场的分发优化​:

好处:
自动做 ABI​​​ 分包​(只下当前CPU架构的 so)自动做 ​语言资源拆分​(只下当前语言)自动做 ​密度拆分​(按屏幕分发资源) 配置(示意):


android {
    bundle {
        language {
            enableSplit = true
        }
        density {
            enableSplit = true
        }
        abi {
            enableSplit = true
        }
    }
}

启用 R8 / ProGuard 混淆与压缩

确保开启:


buildTypes {
    release {
        minifyEnabled true        // 开启代码压缩+混淆
        shrinkResources true      // 删除未使用的资源
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                     'proguard-rules.pro'
    }
}

注意:
线上一定要配好
keep
规则,避免反射导致崩溃。可以开启
-whyareyoukeeping
辅助分析为啥某些类没被清掉。

三、资源瘦身(通常收益最大)

图片资源优化

格式选择

优先使用 矢量图(Vector Drawable) 替代多分辨率 PNG 图标(尤其是图标、简单图形)。位图资源优先考虑:
WebP(有损/无损)适当场景考虑 AVIF(Android 12+,视用户覆盖情况)

压缩图片

使用专业工具离线批量压缩:
TinyPNG、ImageOptim 等(出包前 pipeline 中跑一遍)。 控制图片分辨率:UI 不需要 4K 大图的话就不要用。

去掉冗余分辨率

只保留必要的
xxxhdpi
/
xxhdpi
等,避免同一资源多个密度重复。使用 App Bundle 的 density split 后,可以适度减少本地冗余。

音视频资源

优先采用 ​在线加载+缓存​,避免大体积音视频直接打进包。必须内置的音频:
采用较高压缩率格式(如
ogg

aac
)。控制长度,避免整首歌打入包。 必须内置的视频:
降低分辨率、码率,或做短片/循环片段。考虑 H.265/VP9(取决于最低兼容版本)。

字体资源

如果使用自定义字体:
尽量只内嵌 ​1~2 种主要字体​。使用 ​子集化字体​(只保留需要的字符范围,例如
latin

basic CJK
),减少TTF/OTF大小。多语言字体尽量分拆,通过在线下载或按需包加载。

多语言资源(strings.xml)

若使用 AAB,可以开启语言拆分,安装时只下当前语言。评估是否真的需要支持所有语言:
去掉几乎没有用户的语言资源。或采用在线配置方式加载部分文案,避免全部内置。

移除无用资源

开启
shrinkResources
后,配合工具排查:
Android Studio →
Analyze → Inspect Code
查找 unused resources。注意:被反射 / 动态拼接引用的资源,要通过
tools:keep
或 proguard keep 保护。 清理:
不再使用的旧图标、老主题资源、废弃布局文件。旧的引导页图片、运营活动资源等。

四、代码维度瘦身(dex / 依赖)

依赖梳理与瘦身

检查 Gradle 依赖树


./gradlew app:dependencies

找出:
重复的库(如同一个功能有多个不同依赖)只用到一个小功能却引入非常大的库(如整套 analytics / IM / video SDK)

替换/删除大依赖

大而全的 SDK → 精简版 SDK 或自研轻量实现。多个 JSON 库 → 统一为一个(如 Gson / Moshi),去掉其他。多个网络库 → 统一到 OkHttp/Retrofit 一套。

使用 ​​**
implementation
​ 而非 ​
api
**

避免传递性依赖膨胀其他 module。

代码结构优化

尽量降低方法数量(方法数过多会产生 multi-dex,dex 体积上升):
清理未使用的老代码(弃用模块、旧业务逻辑)。将一些复杂工具类拆分为模块依赖,按需加载或仅在特定版本使用。 对 插件化/热修复框架 做取舍:
部分框架自身体积不小,要评估是否仍有必要。

代码依赖检查方法

在 Android Studio 中检查项目模块(Module)之间的依赖关系,主要有 ​可视化界面查看​、​Gradle 命令分析​、第三方插件辅助 三种方式,覆盖从快速预览到详细分析的场景,以下是具体操作步骤:

1)、可视化界面:快速查看模块依赖(最常用)

Android Studio 内置了依赖关系可视化工具,支持直观查看模块间的直接 / 间接依赖,操作简单:

方式 1:通过「Project Structure」查看

适合快速核对单个模块的依赖(包括本地模块 + 远程库):

打开 Android Studio,进入项目;两种打开方式:
顶部菜单栏 →
File → Project Structure
(快捷键:
Ctrl+Alt+Shift+S
/ Mac
Cmd+Alt+Shift+S
);右侧「Gradle」面板 → 选中项目根目录 → 右键 →
Open Module Settings
; 在左侧面板选择「Modules」,然后选中你要检查的模块(如
app
);切换到右侧「Dependencies」标签页:
列表中「Module dependency」类型即为 ​本地模块依赖​(如
:moduleA

:library
);其他类型(如
Library dependency
)为远程库依赖,可忽略; 若要查看其他模块的依赖,切换左侧模块列表即可。

方式 2:通过「Dependency Graph」可视化依赖树

适合查看 ​整个项目的所有模块依赖关系​(包括模块间的间接依赖),图形化展示更清晰:

顶部菜单栏 →
View → Tool Windows → Gradle
(打开 Gradle 面板);在 Gradle 面板中,展开你的项目 → 选中要分析的模块(如
app
)→ 展开
Tasks → help
;右键点击
dependencies
任务 → 选择
Run '模块名:dependencies'
(如
Run 'app:dependencies'
);执行完成后,底部「Run」面板会输出文本格式的依赖树;若要可视化图形:
顶部菜单栏 →
Analyze → Analyze Dependencies...
;在弹出的窗口中,选择要分析的模块(可多选,如
app

moduleA
),点击「OK」;生成依赖关系图,支持:
拖动节点调整布局;右键节点 →
Show Dependencies
查看该模块的详细依赖;筛选「Only Modules」只显示本地模块依赖(隐藏远程库)。

2)、Gradle 命令:详细分析依赖(支持过滤和导出)

如果需要更精确的依赖分析(如查找重复依赖、间接依赖来源),可通过 Gradle 命令行执行,Android Studio 内置终端直接支持:

打开终端

底部面板 → 切换到「Terminal」标签(若未显示,通过
View → Tool Windows → Terminal
打开)。

核心命令(适用于绝大多数场景)

在终端中输入以下命令,按回车执行:

命令(Windows/Mac/Linux 通用) 作用
./gradlew :app:dependencies 查看 app 模块的所有依赖(本地模块 + 远程库),按配置(debug/release)分类
./gradlew :moduleA:dependencies 查看指定模块(如 moduleA)的依赖
./gradlew dependencies 查看整个项目所有模块的依赖(输出较多,不推荐)
过滤筛选(关键!只看模块依赖)

默认输出包含大量远程库依赖,可通过
grep
(Mac/Linux)或
findstr
(Windows)过滤,只保留本地模块依赖(模块名通常以
:
开头,如
:moduleA
):

Mac​​**/Linux**​:


./gradlew :app:dependencies | grep --color ":module"  # 筛选包含 ":module" 的依赖(模块名通常含 module)# 或精确匹配本地模块(格式为 :模块名)
./gradlew :app:dependencies | grep -E ":([a-zA-Z0-9_]+)" --color

​Windows(​CMD​​**​ 终端)**​:


gradlew :app:dependencies | findstr ":module"

​Windows(​PowerShell​​**​ 终端)**​:


.gradlew :app:dependencies | Select-String ":module"
导出依赖报告(便于分享 / 存档)

将依赖分析结果导出为文本文件,方便后续查看:

Mac/Linux:


./gradlew :app:dependencies > app_dependencies.txt

Windows:


gradlew :app:dependencies > app_dependencies.txt

执行后,项目根目录会生成
app_dependencies.txt
文件,包含完整依赖信息。

3)、第三方插件:增强型依赖分析(复杂项目推荐)

如果项目模块多、依赖关系复杂,可使用第三方插件提供更强大的分析功能:

推荐插件:
Dependency-Check

功能:可视化依赖树、检测重复依赖、冲突依赖报警、导出详细报告;安装步骤:
顶部菜单栏 →
File → Settings → Plugins
(Mac:
Android Studio → Settings → Plugins
);搜索「Dependency-Check」,点击「Install」安装,重启 Android Studio; 使用:
右键项目根目录 →
Dependency Check → Analyze Project
;生成交互式依赖报告,支持筛选模块、查看依赖链路、定位冲突依赖。

其他插件:


Gradle Dependency Helper
:快速搜索依赖、查看最新版本、一键替换依赖;
Dexcount Gradle Plugin
:分析依赖对应的方法数(辅助优化包体积)。

4)、关键说明

模块依赖格式​:本地模块依赖在
build.gradle
中表现为
implementation project(':moduleA')
(Android Gradle Plugin 3.0+),旧版本可能是
compile project(':moduleA')
;​间接依赖​:若
app
依赖
moduleA

moduleA
依赖
moduleB
,则
app
间接依赖
moduleB
,通过「Dependency Graph」或 Gradle 命令可查看这种传递依赖;​依赖冲突​:若多个模块依赖同一库的不同版本,Gradle 会默认选择最高版本,可通过
./gradlew :app:dependencies
查看冲突解决结果(标注
(variant: ...)
)。

通过以上方法,可全面覆盖从「快速预览」到「详细分析」的模块依赖检查需求,根据项目复杂度选择对应方式即可~

五、Native so 库瘦身

只保留必要 ABI

常见 ABI:
armeabi-v7a

arm64-v8a

x86

x86_64
。实战策略:
绝大多数线上版本可以只保留
armeabi-v7a
+
arm64-v8a
。x86 系列设备占比非常低,可改为通过渠道单独提供。 Gradle 配置示例:


android {
    defaultConfig {
        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a"
        }
    }
}

第三方 SDK 的 so 管理

许多 IM、音视频、地图 SDK 默认集成全 ABI 的 so。
尽量使用官方提供的 精简包 或“按需模块化包”。如果 SDK 允许手动删去某些 ABI 目录,可在构建前处理。

自研 Native 代码优化

编译选项优化:
打开
-Os

-Oz
(以减小体积为目标)。去掉不必要的调试符号。 删除无用的 native 方法、旧接口。

六、模块拆分与动态交付(Dynamic Feature)

如果 App 已经比较大(>100MB),可以考虑:

按业务模块拆分
核心流程(登录/首页/基础浏览)作为 base module。低频/重度功能做 ​Dynamic Feature Module​(例如:AR 模块、重度编辑器、游戏、视频编辑等)。 触发更新或安装
用户进入对应页面前再下载对应动态模块。 拆分原则
按“使用频次”和“体积大小”做划分:
高频 + 小体积​:放 base。​低频 + 大体积​:拆动态模块。 避免过度拆分导致开发和维护成本极高。

七、启动后按需加载 / 延迟加载

减少首包必须内容
把不影响“首屏体验”的功能延后加载或在线下发:
运营活动资源辅助工具模块(分享、扫一扫、埋点可稍晚初始化) 在线资源 + ​本地缓存
部分资源(尤其大图 / 大配置)改为首启后后台下载到本地。注意控制失败回退策略,防止无网时完全不可用。

八、构建流程中的自动化检查

持续集成​(​​​CI​**)里增加体积阈值检查**
每次构建统计:
包体积各目录大小(lib/res/assets/dex) 超过阈值时报警,避免长期“野蛮生长”。 自动压缩/转换 Pipeline
在打包前自动执行:
图片压缩脚本字体子集化删除不再使用的资源(配合白名单)

九、具体优化措施

1、assets目录优化

1)、删除字体文件,字体文件由系统预置 (缩减16MB)

App瘦身优化实战

把app的这两个字体文件删除,由系统去集成预置。两个字体文件大约16MB

2、代码依赖优化

使用AS的gradle检查项目依赖树:

如:


+--- project :FlutterHome (*)
+--- org.jetbrains.kotlin:kotlin-parcelize-runtime:1.8.22
|    --- org.jetbrains.kotlin:kotlin-stdlib:1.8.22
|         +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22
|         --- org.jetbrains:annotations:13.0
+--- androidx.databinding:viewbinding:4.2.2
|    --- androidx.annotation:annotation:1.0.0 -> 1.4.0
|         --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22
|         --- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 (*)
+--- androidx.room:room-ktx:2.4.3
|    +--- androidx.room:room-common:2.4.3
|    |    --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    +--- androidx.room:room-runtime:2.4.3 -> 2.4.0
|    |    +--- androidx.room:room-common:2.4.0 -> 2.4.3 (*)
|    |    +--- androidx.sqlite:sqlite-framework:2.2.0
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    --- androidx.sqlite:sqlite:2.2.0
|    |    |         --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    --- androidx.sqlite:sqlite:2.2.0 (*)
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.0 -> 1.8.22 (*)
|    --- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2 -> 1.6.0
|         +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0 -> 1.6.1
|         |    --- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1
|         |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1
|         |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 -> 1.6.0 (c)
|         |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1 (c)
|         |         |    --- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1 (c)
|         |         +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.8.22 (*)
|         |         --- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.0 -> 1.8.22
|         +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.0 -> 1.6.1 (*)
|         --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.8.22 (*)
+--- cn.therouter:router:1.2.2
|    +--- com.google.code.gson:gson:2.9.1 -> 2.10.1
|    +--- androidx.appcompat:appcompat:1.3.0 -> 1.4.1
|    |    +--- androidx.annotation:annotation:1.3.0 -> 1.4.0 (*)
|    |    +--- androidx.core:core:1.7.0 -> 1.8.0
|    |    |    +--- androidx.annotation:annotation:1.2.0 -> 1.4.0 (*)
|    |    |    +--- androidx.annotation:annotation-experimental:1.1.0
|    |    |    +--- androidx.lifecycle:lifecycle-runtime:2.3.1 -> 2.5.1
|    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.arch.core:core-common:2.1.0
|    |    |    |    |    --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    --- androidx.lifecycle:lifecycle-common:2.5.1
|    |    |    |         --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    --- androidx.versionedparcelable:versionedparcelable:1.1.1
|    |    |         +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |         --- androidx.collection:collection:1.0.0 -> 1.2.0
|    |    |              --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    +--- androidx.cursoradapter:cursoradapter:1.0.0
|    |    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    +--- androidx.activity:activity:1.2.4 -> 1.5.1
|    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    +--- androidx.core:core:1.8.0 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-runtime:2.5.1 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1
|    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1
|    |    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core-ktx:1.2.0 -> 1.8.0
|    |    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    |    +--- androidx.core:core:1.8.0 (*)
|    |    |    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    |    |    +--- androidx.lifecycle:lifecycle-livedata-core:2.5.1
|    |    |    |    |    --- androidx.lifecycle:lifecycle-common:2.5.1 (*)
|    |    |    |    +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1 (*)
|    |    |    |    +--- androidx.savedstate:savedstate:1.2.0
|    |    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.20 -> 1.8.22 (*)
|    |    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    |    |    --- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 -> 1.6.0 (*)
|    |    |    +--- androidx.savedstate:savedstate:1.2.0 (*)
|    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    +--- androidx.fragment:fragment:1.3.6 -> 1.5.4
|    |    |    +--- androidx.activity:activity:1.5.1 (*)
|    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    +--- androidx.annotation:annotation-experimental:1.0.0 -> 1.1.0
|    |    |    +--- androidx.collection:collection:1.1.0 -> 1.2.0 (*)
|    |    |    +--- androidx.core:core-ktx:1.2.0 -> 1.8.0 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-livedata-core:2.5.1 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 (*)
|    |    |    +--- androidx.loader:loader:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    |    +--- androidx.lifecycle:lifecycle-livedata:2.0.0 -> 2.5.1
|    |    |    |    |    +--- androidx.arch.core:core-runtime:2.1.0
|    |    |    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    |    |    --- androidx.arch.core:core-common:2.1.0 (*)
|    |    |    |    |    --- androidx.lifecycle:lifecycle-livedata-core:2.5.1 (*)
|    |    |    |    --- androidx.lifecycle:lifecycle-viewmodel:2.0.0 -> 2.5.1 (*)
|    |    |    +--- androidx.savedstate:savedstate:1.2.0 (*)
|    |    |    +--- androidx.viewpager:viewpager:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    |    --- androidx.customview:customview:1.0.0 -> 1.1.0
|    |    |    |         +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |         --- androidx.core:core:1.3.0 -> 1.8.0 (*)
|    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    +--- androidx.appcompat:appcompat-resources:1.4.1
|    |    |    +--- androidx.annotation:annotation:1.2.0 -> 1.4.0 (*)
|    |    |    +--- androidx.core:core:1.0.1 -> 1.8.0 (*)
|    |    |    +--- androidx.vectordrawable:vectordrawable:1.1.0
|    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core:1.1.0 -> 1.8.0 (*)
|    |    |    |    --- androidx.collection:collection:1.1.0 -> 1.2.0 (*)
|    |    |    --- androidx.vectordrawable:vectordrawable-animated:1.1.0
|    |    |         +--- androidx.vectordrawable:vectordrawable:1.1.0 (*)
|    |    |         +--- androidx.interpolator:interpolator:1.0.0
|    |    |         |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |         --- androidx.collection:collection:1.1.0 -> 1.2.0 (*)
|    |    +--- androidx.drawerlayout:drawerlayout:1.0.0
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    --- androidx.customview:customview:1.0.0 -> 1.1.0 (*)
|    |    --- androidx.savedstate:savedstate:1.1.0 -> 1.2.0 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21 -> 1.8.22 (*)
+--- :jxwCwSdk_1.1.1
+--- com.youth.banner:banner:2.0.11
+--- com.github.bumptech.glide:glide:4.11.0
|    +--- com.github.bumptech.glide:gifdecoder:4.11.0
|    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    +--- com.github.bumptech.glide:disklrucache:4.11.0
|    +--- com.github.bumptech.glide:annotations:4.11.0
|    +--- androidx.fragment:fragment:1.0.0 -> 1.5.4 (*)
|    +--- androidx.vectordrawable:vectordrawable-animated:1.0.0 -> 1.1.0 (*)
|    --- androidx.exifinterface:exifinterface:1.0.0 -> 1.3.2
|         --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
+--- com.squareup.retrofit2:adapter-rxjava2:2.3.0
|    +--- com.squareup.retrofit2:retrofit:2.3.0 -> 2.9.0
|    |    --- com.squareup.okhttp3:okhttp:3.14.9 -> 3.14.8
|    |         --- com.squareup.okio:okio:1.17.2 -> 1.17.5
|    --- io.reactivex.rxjava2:rxjava:2.0.0 -> 2.2.6
|         --- org.reactivestreams:reactive-streams:1.0.2 -> 1.0.4
+--- io.reactivex.rxjava2:rxandroid:2.0.1
|    --- io.reactivex.rxjava2:rxjava:2.0.1 -> 2.2.6 (*)
+--- com.trello.rxlifecycle2:rxlifecycle:2.1.0
|    --- io.reactivex.rxjava2:rxjava:2.1.0 -> 2.2.6 (*)
+--- com.trello.rxlifecycle2:rxlifecycle-components:2.1.0
|    +--- com.trello.rxlifecycle2:rxlifecycle-android:2.1.0
|    |    +--- com.trello.rxlifecycle2:rxlifecycle:2.1.0 (*)
|    |    +--- io.reactivex.rxjava2:rxandroid:2.0.1 (*)
|    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    +--- io.reactivex.rxjava2:rxjava:2.1.0 -> 2.2.6 (*)
|    --- androidx.appcompat:appcompat:1.0.0 -> 1.4.1 (*)
+--- org.greenrobot:eventbus:3.0.0
+--- com.ccos.cooui:cache:0.0.1-tzl-10
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> 1.8.22 (*)
+--- com.ccos.cooui:component:0.0.1-tzl-8 -> project :CoreComponent
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- com.ccos.cooui:uiengine:1.8-test-1 -> project :CoreUIEngine
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- com.ccos.cooui:logger:1.7-test-1
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> 1.8.22 (*)
+--- swaiotos.service:push:1.0.636
+--- ccos.core:push-lib:4.1.7
+--- com.coocaa.player:plugin-sdk:2.0.0.1-SNAPSHOT
|    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> 1.8.22 (*)
|    +--- com.tencent.shadow2.core:common:2.5.0.1-8a951ac7-SNAPSHOT
|    +--- com.tencent.shadow2.dynamic:host:2.5.0.1-8a951ac7-SNAPSHOT
|    |    +--- com.tencent.shadow2.dynamic:apk:2.5.0.1-8a951ac7-SNAPSHOT
|    |    |    --- com.tencent.shadow2.core:common:2.5.0.1-8a951ac7-SNAPSHOT
|    |    +--- com.tencent.shadow2.core:common:2.5.0.1-8a951ac7-SNAPSHOT
|    |    --- com.tencent.shadow2.core:utils:2.5.0.1-8a951ac7-SNAPSHOT
|    +--- com.tencent.shadow2.dynamic:host-multi-loader-ext:2.5.0.1-8a951ac7-SNAPSHOT
|    |    +--- com.tencent.shadow2.core:common:2.5.0.1-8a951ac7-SNAPSHOT
|    |    --- com.tencent.shadow2.dynamic:host:2.5.0.1-8a951ac7-SNAPSHOT (*)
|    +--- swaiotos:sal:1.0.601 -> 1.0.718
|    |    +--- swaiotos.base:skysdk:1.0.718
|    |    +--- com.alibaba:fastjson:1.1.71.android -> 1.2.32
|    |    +--- ccos.sdk:rgb-system-sdk:1.0.29
|    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    +--- com.alibaba:fastjson:1.2.32
|    +--- com.github.Justson:Downloader:v5.0.4-androidx
|    +--- androidx.legacy:legacy-support-v4:1.0.0
|    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    +--- androidx.media:media:1.0.0 -> 1.4.1
|    |    |    --- androidx.core:core:1.6.0 -> 1.8.0 (*)
|    |    +--- androidx.legacy:legacy-support-core-utils:1.0.0
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    +--- androidx.documentfile:documentfile:1.0.0
|    |    |    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    +--- androidx.loader:loader:1.0.0 (*)
|    |    |    +--- androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 -> 1.1.0
|    |    |    |    --- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    --- androidx.print:print:1.0.0
|    |    |         --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    +--- androidx.legacy:legacy-support-core-ui:1.0.0
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
|    |    |    +--- androidx.customview:customview:1.0.0 -> 1.1.0 (*)
|    |    |    +--- androidx.viewpager:viewpager:1.0.0 (*)
|    |    |    +--- androidx.coordinatorlayout:coordinatorlayout:1.0.0 -> 1.1.0
|    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core:1.1.0 -> 1.8.0 (*)
|    |    |    |    +--- androidx.customview:customview:1.0.0 -> 1.1.0 (*)
|    |    |    |    --- androidx.collection:collection:1.0.0 -> 1.2.0 (*)
|    |    |    +--- androidx.drawerlayout:drawerlayout:1.0.0 (*)
|    |    |    +--- androidx.slidingpanelayout:slidingpanelayout:1.0.0 -> 1.2.0
|    |    |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    |    |    --- androidx.customview:customview:1.1.0 (*)
|    |    |    +--- androidx.interpolator:interpolator:1.0.0 (*)
|    |    |    +--- androidx.swiperefreshlayout:swiperefreshlayout:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    |    --- androidx.interpolator:interpolator:1.0.0 (*)
|    |    |    +--- androidx.asynclayoutinflater:asynclayoutinflater:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    |    --- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    |    --- androidx.cursoradapter:cursoradapter:1.0.0 (*)
|    |    --- androidx.fragment:fragment:1.0.0 -> 1.5.4 (*)
|    +--- com.coocaa.player:sdk-lite:1.0.27.exo2118.aios-SNAPSHOT
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50 -> 1.8.22 (*)
|    |    +--- com.google.android.exoplayer:exoplayer-core:2.11.8
|    |    +--- com.google.android.exoplayer:exoplayer-hls:2.11.8
|    |    +--- com.google.android.exoplayer:exoplayer-dash:2.11.8
|    |    --- com.google.android.exoplayer:exoplayer-smoothstreaming:2.11.8
|    +--- com.google.protobuf:protobuf-java:3.5.1
|    +--- com.tencent.shadow2.dynamic:manager-multi-loader-ext:2.5.0.1-8a951ac7-SNAPSHOT
|    |    +--- com.tencent.shadow2.core:manager:2.5.0.1-8a951ac7-SNAPSHOT
|    |    |    +--- com.tencent.shadow2.core:load-parameters:2.5.0.1-8a951ac7-SNAPSHOT
|    |    |    --- com.tencent.shadow2.core:utils:2.5.0.1-8a951ac7-SNAPSHOT
|    |    +--- com.tencent.shadow2.dynamic:loader:2.5.0.1-8a951ac7-SNAPSHOT
|    |    --- com.tencent.shadow2.dynamic:manager:2.5.0.1-8a951ac7-SNAPSHOT
|    |         +--- com.tencent.shadow2.core:manager:2.5.0.1-8a951ac7-SNAPSHOT (*)
|    |         --- com.tencent.shadow2.dynamic:loader:2.5.0.1-8a951ac7-SNAPSHOT
|    +--- com.tencent.shadow2.dynamic:manager:2.5.0.1-8a951ac7-SNAPSHOT (*)
|    +--- com.tencent.shadow2.core:manager:2.5.0.1-8a951ac7-SNAPSHOT (*)
|    +--- com.tencent.shadow2.dynamic:loader:2.5.0.1-8a951ac7-SNAPSHOT
|    +--- com.coocaa.player:plugin-api:1.0.0
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> 1.8.22 (*)
|    --- androidx.multidex:multidex:2.0.0
+--- project :LibNetwork
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :LibUtil
|    +--- com.blankj:utilcodex:1.31.1
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :LibPush
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :LibCommon
|    +--- com.ccos.cooui:component:0.0.1-tzl-8 -> project :CoreComponent (*)
|    +--- com.ccos.cooui:logger:1.7-test-1 (*)
|    +--- com.ccos.cooui:uiengine:1.8-test-1 -> project :CoreUIEngine (*)
|    +--- com.ccos.cooui:imageloader:0.0.2-tzl-1 -> project :CoreImageLoader
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- com.ccos.cooui:cache:0.0.1-tzl-10 (*)
|    +--- project :LibNetwork (*)
|    +--- com.ccos.cooui:user:0.0.1-tzl-1 -> project :LibUser
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- project :LibUtil (*)
|    +--- project :LibOperateAppUI
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- project :LibEventTracking
|    |    +--- :gitv-license-online-1.0.22-release
|    |    +--- :newtv-license-submit
|    |    +--- com.coocaa.support:bee-tv:2.0.6
|    |    |    --- com.coocaa.support:bee:2.0.6
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- project :LibDomainVerify
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- com.ccos.cooui:apkdownload:0.0.1-tzl-1
|    +--- project :LibPlugin
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- androidx.multidex:multidex:2.0.0
|    +--- androidx.appcompat:appcompat:1.4.1 (*)
|    +--- androidx.core:core-ktx:1.8.0 (*)
|    +--- androidx.activity:activity-ktx:1.5.0
|    |    +--- androidx.activity:activity:1.5.0 -> 1.5.1 (*)
|    |    +--- androidx.core:core-ktx:1.1.0 -> 1.8.0 (*)
|    |    +--- androidx.lifecycle:lifecycle-runtime-ktx:2.5.0 -> 2.5.1
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-runtime:2.5.1 (*)
|    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    |    --- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 -> 1.6.0 (*)
|    |    +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0 -> 2.5.1
|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1 (*)
|    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    |    |    --- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 -> 1.6.0 (*)
|    |    +--- androidx.savedstate:savedstate-ktx:1.2.0
|    |    |    +--- androidx.savedstate:savedstate:1.2.0 (*)
|    |    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.20 -> 1.8.22 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    +--- cn.therouter:router:1.2.2 (*)
|    +--- swaiotos:sal:1.0.718 (*)
|    +--- swaiotos.service:user:1.0.636
|    |    --- swaiotos.base:skysdk:1.0.636 -> 1.0.718
|    +--- androidx.constraintlayout:constraintlayout:2.1.4
|    +--- ccos.sdk:multimodelsdk:2.0.100
|    +--- com.squareup.okhttp3:okhttp:3.14.8 (*)
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :LibAiCommon
|    +--- project :LibSmartCoolVoice
|    |    +--- com.blankj:utilcodex:1.31.1
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- project :LibFlutterEngine
|    |    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :LibWebview
|    +--- com.blankj:utilcodex:1.31.1
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- project :StudyGradeSolve
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22 (*)
+--- swaiotos:sal:1.0.718 (*)
+--- com.coocaa.support:bee-tv:2.0.6 (*)
+--- androidx.constraintlayout:constraintlayout:2.1.4
+--- androidx.recyclerview:recyclerview:1.2.1
|    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    +--- androidx.core:core:1.3.2 -> 1.8.0 (*)
|    --- androidx.customview:customview:1.0.0 -> 1.1.0 (*)
+--- com.coocaa.sdk:protocol:2.1.6
|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 -> 1.6.0 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 -> 1.8.22 (*)
+--- ccos.sdk:softsettingssdk:1.3.2
+--- ccos.sdk:twocentersdk:1.2.4
+--- androidx.localbroadcastmanager:localbroadcastmanager:1.1.0 (*)
+--- com.tencent.tav:libpag:4.3.62
+--- androidx.viewpager2:viewpager2:1.0.0
|    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    +--- androidx.fragment:fragment:1.1.0 -> 1.5.4 (*)
|    +--- androidx.recyclerview:recyclerview:1.1.0 -> 1.2.1 (*)
|    +--- androidx.core:core:1.1.0 -> 1.8.0 (*)
|    --- androidx.collection:collection:1.1.0 -> 1.2.0 (*)
+--- com.github.yannecer:NCalendar:6.0.0
+--- com.squareup.retrofit2:retrofit:2.9.0 (*)
+--- com.ccos.cooui:http:0.0.1-tzl-10
|    +--- androidx.databinding:viewbinding:4.2.2 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> 1.8.22 (*)
+--- com.squareup.okhttp3:logging-interceptor:3.4.1
|    --- com.squareup.okhttp3:okhttp:3.4.1 -> 3.14.8 (*)
+--- com.localebro:okhttpprofiler:1.0.8
+--- com.github.youlookwhat:ByRecyclerView:1.1.6
+--- com.airbnb.android:lottie:5.2.0
+--- com.squareup.okhttp3:okhttp-sse:3.14.8
|    --- com.squareup.okhttp3:okhttp:3.14.8 (*)
+--- org.jetbrains.anko:anko-sdk15:0.9.1
|    --- org.jetbrains.anko:anko-common:0.9.1
+--- androidx.work:work-runtime-ktx:2.7.1
|    +--- androidx.work:work-runtime:2.7.1
|    |    +--- androidx.annotation:annotation-experimental:1.0.0 -> 1.1.0
|    |    +--- com.google.guava:listenablefuture:1.0 -> 9999.0-empty-to-avoid-conflict-with-guava
|    |    +--- androidx.lifecycle:lifecycle-livedata:2.1.0 -> 2.5.1 (*)
|    |    --- androidx.startup:startup-runtime:1.0.0 -> 1.1.1
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.30 -> 1.8.22 (*)
|    --- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0 -> 1.6.0 (*)
+--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1 (*)
+--- androidx.fragment:fragment-ktx:1.5.4
|    +--- androidx.activity:activity-ktx:1.5.1 -> 1.5.0 (*)
|    +--- androidx.collection:collection-ktx:1.1.0 -> 1.2.0
|    |    +--- androidx.collection:collection:1.2.0 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.5.31 -> 1.8.22 (*)
|    +--- androidx.core:core-ktx:1.2.0 -> 1.8.0 (*)
|    +--- androidx.fragment:fragment:1.5.4 (*)
|    +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.5.1
|    |    +--- androidx.lifecycle:lifecycle-livedata-core:2.5.1 (*)
|    |    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
|    +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1 (*)
|    +--- androidx.savedstate:savedstate-ktx:1.2.0 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 -> 1.8.22 (*)
+--- androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 (*)
+--- com.google.android.material:material:1.3.0 -> 1.4.0
|    +--- androidx.annotation:annotation:1.0.1 -> 1.4.0 (*)
|    +--- androidx.appcompat:appcompat:1.1.0 -> 1.4.1 (*)
|    +--- androidx.cardview:cardview:1.0.0
|    |    --- androidx.annotation:annotation:1.0.0 -> 1.4.0 (*)
|    +--- androidx.coordinatorlayout:coordinatorlayout:1.1.0 (*)
|    +--- androidx.constraintlayout:constraintlayout:2.0.1 -> 2.1.4
|    +--- androidx.core:core:1.5.0 -> 1.8.0 (*)
|    +--- androidx.dynamicanimation:dynamicanimation:1.0.0
|    |    +--- androidx.core:core:1.0.0 -> 1.8.0 (*)
|    |    +--- androidx.collection:collection:1.0.0 -> 1.2.0 (*)
|    |    --- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
|    +--- androidx.annotation:annotation-experimental:1.0.0 -> 1.1.0
|    +--- androidx.fragment:fragment:1.0.0 -> 1.5.4 (*)
|    +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.5.1 (*)
|    +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.2.1 (*)
|    +--- androidx.transition:transition:1.2.0 -> 1.4.1
|    |    +--- androidx.annotation:annotation:1.1.0 -> 1.4.0 (*)
|    |    --- androidx.core:core:1.1.0 -> 1.8.0 (*)
|    +--- androidx.vectordrawable:vectordrawable:1.1.0 (*)
|    --- androidx.viewpager2:viewpager2:1.0.0 (*)
+--- com.squareup.leakcanary:leakcanary-android:2.10
|    --- com.squareup.leakcanary:leakcanary-object-watcher-android:2.10
|         +--- com.squareup.leakcanary:leakcanary-object-watcher-android-core:2.10
|         |    +--- com.squareup.leakcanary:leakcanary-object-watcher:2.10
|         |    |    --- com.squareup.leakcanary:shark-log:2.10
|         |    --- com.squareup.leakcanary:leakcanary-android-utils:2.10
|         |         --- com.squareup.leakcanary:shark-log:2.10
|         --- org.jetbrains.kotlin:kotlin-stdlib:1.4.21 -> 1.8.22 (*)
+--- com.coocaa.terminal:leakcanary-core:0.0.1-tzl-10
|    +--- com.squareup.leakcanary:shark-android:2.10
|    |    --- com.squareup.leakcanary:shark:2.10
|    |         --- com.squareup.leakcanary:shark-graph:2.10
|    |              --- com.squareup.leakcanary:shark-hprof:2.10
|    |                   --- com.squareup.leakcanary:shark-log:2.10
|    +--- com.squareup.leakcanary:leakcanary-object-watcher-android-core:2.10 (*)
|    +--- com.squareup.leakcanary:leakcanary-object-watcher-android-androidx:2.10
|    |    --- com.squareup.leakcanary:leakcanary-object-watcher-android-core:2.10 (*)
|    +--- com.squareup.leakcanary:leakcanary-object-watcher-android-support-fragments:2.10
|    |    --- com.squareup.leakcanary:leakcanary-object-watcher-android-core:2.10 (*)
|    --- org.jetbrains.kotlin:kotlin-stdlib:kotlinLib -> 1.8.22 (*)
+--- com.guolindev.glance:glance:1.1.0
|    --- androidx.databinding:viewbinding:4.2.1 -> 4.2.2 (*)
+--- project :CoreComponent (*)
+--- project :CoreUIEngine (*)
+--- project :CoreImageLoader (*)
+--- project :LibUser (*)
+--- org.jetbrains.kotlin:kotlin-parcelize-runtime:{strictly 1.8.22} -> 1.8.22 (c)
+--- androidx.databinding:viewbinding:{strictly 4.2.2} -> 4.2.2 (c)
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:{strictly 1.8.22} -> 1.8.22 (c)
+--- com.squareup.leakcanary:leakcanary-android:{strictly 2.10} -> 2.10 (c)
+--- com.coocaa.terminal:leakcanary-core:{strictly 0.0.1-tzl-10} -> 0.0.1-tzl-10 (c)
+--- com.guolindev.glance:glance:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.recyclerview:recyclerview:{strictly 1.2.1} -> 1.2.1 (c)
+--- androidx.lifecycle:lifecycle-runtime-ktx:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.lifecycle:lifecycle-viewmodel-ktx:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.constraintlayout:constraintlayout:{strictly 2.1.4} -> 2.1.4 (c)
+--- com.google.android.material:material:{strictly 1.4.0} -> 1.4.0 (c)
+--- androidx.localbroadcastmanager:localbroadcastmanager:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.viewpager2:viewpager2:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.room:room-ktx:{strictly 2.4.3} -> 2.4.3 (c)
+--- cn.therouter:router:{strictly 1.2.2} -> 1.2.2 (c)
+--- com.youth.banner:banner:{strictly 2.0.11} -> 2.0.11 (c)
+--- com.github.bumptech.glide:glide:{strictly 4.11.0} -> 4.11.0 (c)
+--- com.squareup.retrofit2:adapter-rxjava2:{strictly 2.3.0} -> 2.3.0 (c)
+--- com.squareup.retrofit2:retrofit:{strictly 2.9.0} -> 2.9.0 (c)
+--- io.reactivex.rxjava2:rxandroid:{strictly 2.0.1} -> 2.0.1 (c)
+--- com.trello.rxlifecycle2:rxlifecycle:{strictly 2.1.0} -> 2.1.0 (c)
+--- com.trello.rxlifecycle2:rxlifecycle-components:{strictly 2.1.0} -> 2.1.0 (c)
+--- org.greenrobot:eventbus:{strictly 3.0.0} -> 3.0.0 (c)
+--- com.ccos.cooui:cache:{strictly 0.0.1-tzl-10} -> 0.0.1-tzl-10 (c)
+--- swaiotos:sal:{strictly 1.0.718} -> 1.0.718 (c)
+--- com.ccos.cooui:logger:{strictly 1.7-test-1} -> 1.7-test-1 (c)
+--- swaiotos.service:push:{strictly 1.0.636} -> 1.0.636 (c)
+--- ccos.core:push-lib:{strictly 4.1.7} -> 4.1.7 (c)
+--- com.coocaa.player:plugin-sdk:{strictly 2.0.0.1-SNAPSHOT} -> 2.0.0.1-SNAPSHOT (c)
+--- com.squareup.okhttp3:logging-interceptor:{strictly 3.4.1} -> 3.4.1 (c)
+--- com.ccos.cooui:http:{strictly 0.0.1-tzl-10} -> 0.0.1-tzl-10 (c)
+--- com.localebro:okhttpprofiler:{strictly 1.0.8} -> 1.0.8 (c)
+--- com.coocaa.support:bee-tv:{strictly 2.0.6} -> 2.0.6 (c)
+--- ccos.sdk:softsettingssdk:{strictly 1.3.2} -> 1.3.2 (c)
+--- ccos.sdk:twocentersdk:{strictly 1.2.4} -> 1.2.4 (c)
+--- com.tencent.tav:libpag:{strictly 4.3.62} -> 4.3.62 (c)
+--- org.jetbrains.anko:anko-sdk15:{strictly 0.9.1} -> 0.9.1 (c)
+--- androidx.work:work-runtime-ktx:{strictly 2.7.1} -> 2.7.1 (c)
+--- androidx.fragment:fragment-ktx:{strictly 1.5.4} -> 1.5.4 (c)
+--- com.squareup.okhttp3:okhttp-sse:{strictly 3.14.8} -> 3.14.8 (c)
+--- com.coocaa.sdk:protocol:{strictly 2.1.6} -> 2.1.6 (c)
+--- com.github.yannecer:NCalendar:{strictly 6.0.0} -> 6.0.0 (c)
+--- com.github.youlookwhat:ByRecyclerView:{strictly 1.1.6} -> 1.1.6 (c)
+--- com.airbnb.android:lottie:{strictly 5.2.0} -> 5.2.0 (c)
+--- org.jetbrains.kotlin:kotlin-stdlib:{strictly 1.8.22} -> 1.8.22 (c)
+--- androidx.annotation:annotation:{strictly 1.4.0} -> 1.4.0 (c)
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:{strictly 1.8.22} -> 1.8.22 (c)
+--- androidx.room:room-common:{strictly 2.4.3} -> 2.4.3 (c)
+--- androidx.room:room-runtime:{strictly 2.4.0} -> 2.4.0 (c)
+--- org.jetbrains.kotlinx:kotlinx-coroutines-android:{strictly 1.6.0} -> 1.6.0 (c)
+--- com.google.code.gson:gson:{strictly 2.10.1} -> 2.10.1 (c)
+--- androidx.appcompat:appcompat:{strictly 1.4.1} -> 1.4.1 (c)
+--- com.github.bumptech.glide:gifdecoder:{strictly 4.11.0} -> 4.11.0 (c)
+--- com.github.bumptech.glide:disklrucache:{strictly 4.11.0} -> 4.11.0 (c)
+--- com.github.bumptech.glide:annotations:{strictly 4.11.0} -> 4.11.0 (c)
+--- androidx.fragment:fragment:{strictly 1.5.4} -> 1.5.4 (c)
+--- androidx.vectordrawable:vectordrawable-animated:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.exifinterface:exifinterface:{strictly 1.3.2} -> 1.3.2 (c)
+--- io.reactivex.rxjava2:rxjava:{strictly 2.2.6} -> 2.2.6 (c)
+--- com.trello.rxlifecycle2:rxlifecycle-android:{strictly 2.1.0} -> 2.1.0 (c)
+--- com.tencent.shadow2.core:common:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.dynamic:host:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.dynamic:host-multi-loader-ext:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.alibaba:fastjson:{strictly 1.2.32} -> 1.2.32 (c)
+--- com.github.Justson:Downloader:{strictly v5.0.4-androidx} -> v5.0.4-androidx (c)
+--- androidx.legacy:legacy-support-v4:{strictly 1.0.0} -> 1.0.0 (c)
+--- com.coocaa.player:sdk-lite:{strictly 1.0.27.exo2118.aios-SNAPSHOT} -> 1.0.27.exo2118.aios-SNAPSHOT (c)
+--- com.google.protobuf:protobuf-java:{strictly 3.5.1} -> 3.5.1 (c)
+--- com.tencent.shadow2.dynamic:manager-multi-loader-ext:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.dynamic:manager:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.core:manager:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.dynamic:loader:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.coocaa.player:plugin-api:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.multidex:multidex:{strictly 2.0.0} -> 2.0.0 (c)
+--- com.blankj:utilcodex:{strictly 1.31.1} -> 1.31.1 (c)
+--- com.ccos.cooui:apkdownload:{strictly 0.0.1-tzl-1} -> 0.0.1-tzl-1 (c)
+--- androidx.core:core-ktx:{strictly 1.8.0} -> 1.8.0 (c)
+--- androidx.activity:activity-ktx:{strictly 1.5.0} -> 1.5.0 (c)
+--- swaiotos.service:user:{strictly 1.0.636} -> 1.0.636 (c)
+--- ccos.sdk:multimodelsdk:{strictly 2.0.100} -> 2.0.100 (c)
+--- com.squareup.okhttp3:okhttp:{strictly 3.14.8} -> 3.14.8 (c)
+--- swaiotos.base:skysdk:{strictly 1.0.718} -> 1.0.718 (c)
+--- ccos.sdk:rgb-system-sdk:{strictly 1.0.29} -> 1.0.29 (c)
+--- com.coocaa.support:bee:{strictly 2.0.6} -> 2.0.6 (c)
+--- androidx.core:core:{strictly 1.8.0} -> 1.8.0 (c)
+--- androidx.customview:customview:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.collection:collection:{strictly 1.2.0} -> 1.2.0 (c)
+--- org.jetbrains.anko:anko-common:{strictly 0.9.1} -> 0.9.1 (c)
+--- androidx.work:work-runtime:{strictly 2.7.1} -> 2.7.1 (c)
+--- androidx.lifecycle:lifecycle-viewmodel:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.collection:collection-ktx:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.lifecycle:lifecycle-livedata-core-ktx:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.savedstate:savedstate-ktx:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.lifecycle:lifecycle-runtime:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.cardview:cardview:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.coordinatorlayout:coordinatorlayout:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.dynamicanimation:dynamicanimation:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.annotation:annotation-experimental:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.transition:transition:{strictly 1.4.1} -> 1.4.1 (c)
+--- androidx.vectordrawable:vectordrawable:{strictly 1.1.0} -> 1.1.0 (c)
+--- com.squareup.leakcanary:leakcanary-object-watcher-android:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:shark-android:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:leakcanary-object-watcher-android-core:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:leakcanary-object-watcher-android-androidx:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:leakcanary-object-watcher-android-support-fragments:{strictly 2.10} -> 2.10 (c)
+--- org.jetbrains.kotlin:kotlin-stdlib-common:{strictly 1.8.22} -> 1.8.22 (c)
+--- org.jetbrains:annotations:{strictly 13.0} -> 13.0 (c)
+--- androidx.sqlite:sqlite-framework:{strictly 2.2.0} -> 2.2.0 (c)
+--- androidx.sqlite:sqlite:{strictly 2.2.0} -> 2.2.0 (c)
+--- org.jetbrains.kotlinx:kotlinx-coroutines-core:{strictly 1.6.1} -> 1.6.1 (c)
+--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:{strictly 1.6.1} -> 1.6.1 (c)
+--- androidx.cursoradapter:cursoradapter:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.activity:activity:{strictly 1.5.1} -> 1.5.1 (c)
+--- androidx.appcompat:appcompat-resources:{strictly 1.4.1} -> 1.4.1 (c)
+--- androidx.drawerlayout:drawerlayout:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.savedstate:savedstate:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.lifecycle:lifecycle-livedata-core:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.lifecycle:lifecycle-viewmodel-savedstate:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.loader:loader:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.viewpager:viewpager:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.interpolator:interpolator:{strictly 1.0.0} -> 1.0.0 (c)
+--- org.reactivestreams:reactive-streams:{strictly 1.0.4} -> 1.0.4 (c)
+--- com.tencent.shadow2.dynamic:apk:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.tencent.shadow2.core:utils:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- androidx.media:media:{strictly 1.4.1} -> 1.4.1 (c)
+--- androidx.legacy:legacy-support-core-utils:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.legacy:legacy-support-core-ui:{strictly 1.0.0} -> 1.0.0 (c)
+--- com.google.android.exoplayer:exoplayer-core:{strictly 2.11.8} -> 2.11.8 (c)
+--- com.google.android.exoplayer:exoplayer-hls:{strictly 2.11.8} -> 2.11.8 (c)
+--- com.google.android.exoplayer:exoplayer-dash:{strictly 2.11.8} -> 2.11.8 (c)
+--- com.google.android.exoplayer:exoplayer-smoothstreaming:{strictly 2.11.8} -> 2.11.8 (c)
+--- com.tencent.shadow2.core:load-parameters:{strictly 2.5.0.1-8a951ac7-SNAPSHOT} -> 2.5.0.1-8a951ac7-SNAPSHOT (c)
+--- com.squareup.okio:okio:{strictly 1.17.5} -> 1.17.5 (c)
+--- androidx.versionedparcelable:versionedparcelable:{strictly 1.1.1} -> 1.1.1 (c)
+--- com.google.guava:listenablefuture:{strictly 9999.0-empty-to-avoid-conflict-with-guava} -> 9999.0-empty-to-avoid-conflict-with-guava (c)
+--- androidx.lifecycle:lifecycle-livedata:{strictly 2.5.1} -> 2.5.1 (c)
+--- androidx.startup:startup-runtime:{strictly 1.1.1} -> 1.1.1 (c)
+--- androidx.arch.core:core-common:{strictly 2.1.0} -> 2.1.0 (c)
+--- androidx.lifecycle:lifecycle-common:{strictly 2.5.1} -> 2.5.1 (c)
+--- com.squareup.leakcanary:shark:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:leakcanary-object-watcher:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:leakcanary-android-utils:{strictly 2.10} -> 2.10 (c)
+--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:{strictly 1.6.1} -> 1.6.1 (c)
+--- androidx.documentfile:documentfile:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.print:print:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.slidingpanelayout:slidingpanelayout:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.swiperefreshlayout:swiperefreshlayout:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.asynclayoutinflater:asynclayoutinflater:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.arch.core:core-runtime:{strictly 2.1.0} -> 2.1.0 (c)
+--- com.squareup.leakcanary:shark-graph:{strictly 2.10} -> 2.10 (c)
+--- com.squareup.leakcanary:shark-log:{strictly 2.10} -> 2.10 (c)
--- com.squareup.leakcanary:shark-hprof:{strictly 2.10} -> 2.10 (c)

1)、剔除多余的子模块 (缩减4M)

App瘦身优化实战

剔除多余模块,不参与编译打包,大概减小4M

2)、剔除多余的子模块 (缩减6M)

App瘦身优化实战

剔除多余模块,不参与编译打包,大概减小6M

3、资源优化

1)、删除未使用的资源 (400K)

资源文件夹上右键—>Refactor—>Remove Unused Resource,之后选择Preview

App瘦身优化实战

App瘦身优化实战

切记先选Preview进行预览,否则AS会直接进行删除,有可能会误删除资源。

App瘦身优化实战

App瘦身优化实战

这些就是未使用的资源,不过最好手动校验需不需要删除。

2)、压缩图片资源

AS gradle中实现task压缩图片:


task convertToWebPRealAuto {
​    ​doLast {
​        ​println("开始真实的WebP转换优化...")

        def resDir = file("src/jxw/res")
        if (!resDir.exists()) {
            println("资源目录不存在: ${resDir.absolutePath}")
            return
        }

        // 创建备份目录
        def backupDir = new File(resDir.parentFile, "res_backup_${System.currentTimeMillis()}")
        if (!backupDir.exists()) {
            backupDir.mkdirs()
        }

        def convertedFiles = []
        def backedUpFiles = []
        def originalFilesDeleted = []
        def skippedFiles = []
        def failedFiles = []
        def ignoredFiles = [] // 新增:记录被忽略的文件(转换后变大的文件)
        def totalSizeSaved = 0L
        def totalOriginalSize = 0L

        // 改进的cwebp工具检测
        def cwebpAvailable = false
        def cwebpPath = ""

        println("🔍 正在检测cwebp工具...")

        // 方法1: 直接尝试执行cwebp命令(最可靠的方法)
        try {
            def testProcess = ["cwebp", "-version"].execute()
            testProcess.waitFor()
            if (testProcess.exitValue() == 0) {
                cwebpPath = "cwebp"
                cwebpAvailable = true
                println("✅ 方法1: 通过直接执行找到cwebp工具")
            }
        } catch (Exception e) {
            println("❌ 方法1失败: ${e.message}")
        }

        // 方法2: 如果方法1失败,尝试使用where命令(Windows专用)
        if (!cwebpAvailable) {
            try {
                def whereProcess = ["cmd", "/c", "where", "cwebp"].execute()
                whereProcess.waitFor()
                if (whereProcess.exitValue() == 0) {
                    def output = whereProcess.text.trim()
                    if (output && !output.contains("INFO:") && output.contains("cwebp")) {
                        cwebpPath = output.split("
")[0].trim()
                        cwebpAvailable = true
                        println("✅ 方法2: 通过where命令找到cwebp工具: $cwebpPath")
                    }
                }
            } catch (Exception e) {
                println("❌ 方法2失败: ${e.message}")
            }
        }

        // 方法3: 检查常见安装路径
        if (!cwebpAvailable) {
            def commonPaths = [
                    "C:\libwebp\bin\cwebp.exe",
                    "C:\Program Files\libwebp\bin\cwebp.exe",
                    "C:\Program Files (x86)\libwebp\bin\cwebp.exe",
                    "D:\libwebp-1.6.0-windows-x64\bin\cwebp.exe",
                    "/usr/local/bin/cwebp",
                    "/usr/bin/cwebp",
                    "/opt/homebrew/bin/cwebp" // M1 Mac
            ]

            for (path in commonPaths) {
                def file = new File(path)
                if (file.exists()) {
                    cwebpPath = path
                    cwebpAvailable = true
                    println("✅ 方法3: 通过路径检查找到cwebp工具: $path")
                    break
                }
            }
        }

        // 方法4: 搜索整个系统(最后的手段)
        if (!cwebpAvailable) {
            println("🔍 正在搜索系统cwebp工具...")
            try {
                // 在Windows上搜索
                def searchProcess
                if (System.getProperty("os.name").toLowerCase().contains("windows")) {
                    searchProcess = ["cmd", "/c", "dir", "/s", "/b", "cwebp.exe"].execute()
                } else {
                    searchProcess = ["find", "/", "-name", "cwebp", "-type", "f", "2>/dev/null"].execute()
                }

                searchProcess.waitFor(5000) // 最多等待5秒
                if (searchProcess.exitValue() == 0) {
                    def output = searchProcess.text.trim()
                    if (output) {
                        def paths = output.split("
")
                        if (paths.size() > 0) {
                            cwebpPath = paths[0].trim()
                            cwebpAvailable = true
                            println("✅ 方法4: 通过系统搜索找到cwebp工具: $cwebpPath")
                        }
                    }
                }
            } catch (Exception e) {
                println("❌ 方法4失败: ${e.message}")
            }
        }

        if (!cwebpAvailable) {
            println("❌ 未找到cwebp工具,无法进行真实的WebP转换")
            println("")
            println("💡 请按以下步骤安装WebP工具:")
            println("1. 下载 libwebp: https://developers.google.com/speed/webp/download")
            println("2. 解压到 C:\libwebp")
            println("3. 将 C:\libwebp\bin 添加到系统PATH环境变量")
            println("4. 重启命令行或IDE")
            println("5. 验证安装: 在命令行输入 'cwebp -version'")
            println("")
            println("或者运行: ./gradlew installWebPTools 查看详细安装指南")
            return
        }

        // 验证cwebp工具是否真正可用
        println("🔍 验证cwebp工具功能...")
        try {
            def verifyProcess
            if (cwebpPath.endsWith(".exe")) {
                verifyProcess = ["cmd", "/c", cwebpPath, "-version"].execute()
            } else {
                verifyProcess = [cwebpPath, "-version"].execute()
            }
            verifyProcess.waitFor()
            if (verifyProcess.exitValue() == 0) {
                println("✅ cwebp工具验证成功: ${verifyProcess.text.trim()}")
            } else {
                println("❌ cwebp工具验证失败")
                cwebpAvailable = false
            }
        } catch (Exception e) {
            println("❌ cwebp工具验证异常: ${e.message}")
            cwebpAvailable = false
        }

        if (!cwebpAvailable) {
            println("❌ cwebp工具不可用,请检查安装")
            return
        }

        // 遍历所有资源目录
        resDir.eachDir { ​dir ->
​            ​if (dir.name.startsWith('mipmap-') || dir.name.startsWith('drawable-')) {
                println("处理目录: ${dir.name}")

                // 创建对应的备份目录
                def backupSubDir = new File(backupDir, dir.name)
                if (!backupSubDir.exists()) {
                    backupSubDir.mkdirs()
                }

                dir.eachFile { ​file ->
​                    ​if (file.name.endsWith('.png') || file.name.endsWith('.jpg') || file.name.endsWith('.jpeg')) {
                        def fileName = file.name.substring(0, file.name.lastIndexOf('.'))
                        def webpFile = new File(dir, "${fileName}.webp")
                        def fileSize = file.length()
                        totalOriginalSize += fileSize

                        // 先备份原始文件
                        def backupFile = new File(backupSubDir, file.name)
                        try {
                            file.withInputStream { ​input ->
​                                ​backupFile.withOutputStream { ​output ->
​                                    ​output << input
                                }
​                            }
​                            ​backedUpFiles.add(file.name)
                            println("✓ 已备份: ${file.name}")
                        } catch (Exception e) {
                            println("⚠ 备份失败: ${file.name} - ${e.message}")
                            return // 跳过这个文件
                        }

                        // 如果WebP文件已存在且有效,跳过转换
                        if (webpFile.exists() && webpFile.length() > 100) {
                            def webpSize = webpFile.length()
                            skippedFiles.add([file: file.name, reason: "有效的WebP文件已存在(${webpSize} bytes)"])
                            println("⏭ 跳过: ${file.name} (有效的WebP文件已存在)")
                            return
                        }

                        println("🔄 开始转换: ${file.name} → ${webpFile.name}")

                        try {
                            // 使用cwebp进行真实的WebP转换
                            def cmd = []
                            if (cwebpPath.endsWith(".exe")) {
                                cmd = ["cmd", "/c", cwebpPath, "-q", "80", file.absolutePath, "-o", webpFile.absolutePath]
                            } else {
                                cmd = [cwebpPath, "-q", "80", file.absolutePath, "-o", webpFile.absolutePath]
                            }

                            println("  执行命令: ${cmd.join(' ')}")
                            def process = cmd.execute()
                            process.waitFor()

                            if (process.exitValue() == 0) {
                                // 检查生成的WebP文件是否有效
                                if (webpFile.exists() && webpFile.length() > 100) {
                                    def webpSize = webpFile.length()
                                    def savings = fileSize - webpSize

                                    // 关键修复:检查转换后文件是否变大
                                    if (webpSize < fileSize) {
                                        // 转换后文件变小,保留WebP文件
                                        totalSizeSaved += savings
                                        convertedFiles.add(file.name)
                                        println("✅ 转换成功: ${file.name} (${fileSize} → ${webpSize} bytes, 节省 ${String.format("%.1f", savings/1024.0)} KB)")

                                        // 转换成功后删除原始文件
                                        if (file.delete()) {
                                            originalFilesDeleted.add(file.name)
                                            println("   🗑️ 已删除原始文件")
                                        } else {
                                            println("   ⚠️ 删除原始文件失败")
                                        }
                                    } else {
                                        // 转换后文件变大,忽略转换结果
                                        def increase = webpSize - fileSize
                                        ignoredFiles.add([file: file.name, originalSize: fileSize, webpSize: webpSize, increase: increase])
                                        println("⚠️ 忽略转换: ${file.name} (转换后变大: ${fileSize} → ${webpSize} bytes, 增加 ${String.format("%.1f", increase/1024.0)} KB)")

                                        // 删除生成的WebP文件,保留原始文件
                                        if (webpFile.delete()) {
                                            println("   🗑️ 已删除无效的WebP文件")
                                        }
                                    }
                                } else {
                                    failedFiles.add([file: file.name, reason: "生成的WebP文件无效或太小"])
                                    println("❌ 转换失败: 生成的WebP文件无效")
                                    if (webpFile.exists()) {
                                        webpFile.delete() // 清理无效文件
                                    }
                                }
                            } else {
                                def errorOutput = process.err.text ?: process.text ?: "未知错误"
                                failedFiles.add([file: file.name, reason: "cwebp转换失败: ${errorOutput.trim()}"])
                                println("❌ 转换失败: ${errorOutput.trim()}")
                                if (webpFile.exists()) {
                                    webpFile.delete() // 清理失败的文件
                                }
                            }
                        } catch (Exception e) {
                            failedFiles.add([file: file.name, reason: "执行错误: ${e.message}"])
                            println("❌ 转换异常: ${e.message}")
                        }
                    }
                }
​            ​}
        }

​        ​// 统计结果
        def backupFileCount = 0
        backupDir.eachDirRecurse { ​dir ->
​            ​backupFileCount += dir.listFiles().size()
        }

​        ​// 输出详细报告
        println("
" + "="*60)
        println("📊 真实WebP转换报告")
        println("="*60)
        println("🛠️  使用的工具: $cwebpPath")
        println("📁 处理目录: ${resDir.absolutePath}")
        println("💾 原始总大小: ${String.format("%.2f", totalOriginalSize/1024/1024)} MB")
        println("💰 实际节省: ${String.format("%.2f", totalSizeSaved/1024/1024)} MB")
        if (totalOriginalSize > 0) {
            println("📈 压缩率: ${String.format("%.1f", totalSizeSaved*100/totalOriginalSize)}%")
        }
        println("✅ 成功转换: ${convertedFiles.size()} ​个文件")
        println("⚠️ 忽略转换: ${ignoredFiles.size()} ​个文件(转换后变大)")
        println("❌ 转换失败: ${failedFiles.size()} ​个文件")
        println("⏭ 跳过文件: ${skippedFiles.size()} ​个")
        println("💾 备份文件: ${backupFileCount} ​个")

        if (ignoredFiles.size() > 0) {
            println("
🟡 忽略文件详情(转换后变大):")
            ignoredFiles.take(5).each { ​ignore ->
​                ​println("   - ${ignore.file}: ${ignore.originalSize} → ${ignore.webpSize} bytes (增加 ${String.format("%.1f", ignore.increase/1024.0)} KB)")
            }
​            ​if (ignoredFiles.size() > 5) {
                println("   ... 还有 ${ignoredFiles.size() - 5} ​个文件被忽略")
            }
        }

        if (failedFiles.size() > 0) {
            println("
🔴 失败文件详情:")
            failedFiles.take(5).each { ​fail ->
​                ​println("   - ${fail.file}: ${fail.reason}")
            }
​            ​if (failedFiles.size() > 5) {
                println("   ... 还有 ${failedFiles.size() - 5} ​个文件转换失败")
            }
        }

        if (backupFileCount > 0) {
            println("
✅ 备份完成!目录: ${backupDir.absolutePath}")
            println("💡 如需恢复原始文件,可从备份目录复制")
        } else {
            backupDir.deleteDir()
            println("ℹ️ 备份目录已清理")
        }
    }
}

图片压缩功能:忽略掉转换后反而变大的情况

新增忽略文件统计:添加了
ignoredFiles
列表来记录转换后变大的文件文件大小比较逻辑:在转换成功后检查
webpSize < fileSize
,只有当WebP文件确实更小时才保留智能处理:如果转换后文件变大,会自动删除生成的WebP文件,保留原始PNG/JPG文件详细报告:在统计报告中显示被忽略的文件数量和详情清晰的日志:用”⚠️ 忽略转换”标识这类情况,并显示具体增加的大小

现在当遇到类似
mask_voice.png
这种转换后反而变大的情况时,系统会自动忽略转换结果,保留原始文件,避免不必要的体积增加。

运行结果如下:


> Task :Study:convertToWebPRealAuto
开始真实的WebP转换优化...
🔍 正在检测cwebp工具...
❌ 方法1失败: Cannot run program "cwebp": CreateProcess error=2, 系统找不到指定的文件。
✅ 方法3: 通过路径检查找到cwebp工具: D:libwebp-1.6.0-windows-x64incwebp.exe
🔍 验证cwebp工具功能...
✅ cwebp工具验证成功: 1.6.0
libsharpyuv: 0.4.2
处理目录: drawable-hdpi
✓ 已备份: account_avatar_default.png
🔄 开始转换: account_avatar_default.png → account_avatar_default.webp
  执行命令: cmd /c D:libwebp-1.6.0-windows-x64incwebp.exe -q 80 E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-hdpiaccount_avatar_default.png -o E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-hdpiaccount_avatar_default.webp
✅ 转换成功: account_avatar_default.png (22009 → 8414 bytes, 节省 13.3 KB)
   🗑️ 已删除原始文件
......
✓ 已备份: ic_gm_home_popu.png
🔄 开始转换: ic_gm_home_popu.png → ic_gm_home_popu.webp
  执行命令: cmd /c D:libwebp-1.6.0-windows-x64incwebp.exe -q 80 E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-hdpiic_gm_home_popu.png -o E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-hdpiic_gm_home_popu.webp
⚠️ 忽略转换: ic_gm_home_popu.png (转换后变大: 129 → 154 bytes, 增加 0.0 KB)
   🗑️ 已删除无效的WebP文件
......
✓ 已备份: zwzx.png
🔄 开始转换: zwzx.png → zwzx.webp
  执行命令: cmd /c D:libwebp-1.6.0-windows-x64incwebp.exe -q 80 E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-xhdpizwzx.png -o E:projectstudyV3.0-Study-AitutorappStudysrcjxw
esdrawable-xhdpizwzx.webp
✅ 转换成功: zwzx.png (19853 → 2978 bytes, 节省 16.5 KB)
   🗑️ 已删除原始文件

============================================================
📊 真实WebP转换报告
============================================================
🛠️  使用的工具: D:libwebp-1.6.0-windows-x64incwebp.exe
📁 处理目录: E:projectstudyV3.0-Study-AitutorappStudysrcjxw
es
💾 原始总大小: 7.11 MB
💰 实际节省: 4.47 MB
📈 压缩率: 62.8%
✅ 成功转换: 379 个文件
⚠️ 忽略转换: 18 个文件(转换后变大)
❌ 转换失败: 0 个文件
⏭ 跳过文件: 0 个
💾 备份文件: 397 个

十、总结

序号 瘦身优化说明 大小 MB
1 瘦身前 117.5
2 剔除字体文件以及压缩部分png图片 99.3
3 剔除多余模块参与编译打包 95.4
4 剔除多余模块参与编译打包 89.6
5 核心子模块压缩图片及删除不用资源 88.5
6 删除不用的资源 88.1
© 版权声明

相关文章

暂无评论

none
暂无评论...