在这里插入图片描述

📖 引言

在应用商店里,两个功能类似的App,一个10MB,一个100MB,你会下载哪个?大多数用户会选择更小的那个。包体积不仅仅是数字,它直接影响着下载转化率、安装时间、存储空间,甚至用户的卸载意愿。

对于「民族图鉴」这样的工具类应用来说,包体积尤为重要。用户下载它是为了快速查询民族信息,而不是为了等待一个几百MB的安装包下载完成。如果包体积太大,用户可能在下载页面就放弃了;即使下载了,也可能因为占用空间太多而卸载。

你可能会问:我的代码没写多少,为什么安装包这么大?图片资源到底占了多少空间?混淆和压缩真的有用吗?WebP格式能省多少?什么是Tree Shaking?资源分包又是怎么回事?

这些问题都是包体积优化的核心。很多开发者对包体积的认知停留在"压缩图片"这个层面,但实际上,包体积优化是一个系统工程——从代码到资源,从构建配置到分发策略,每个环节都有优化空间。

本文将从鸿蒙安装包的构成讲起,全面介绍包体积优化的各种手段:代码优化、资源优化、图片优化、字体优化、按需加载、资源分包。结合「民族图鉴」项目的实际情况,带你完成一次完整的应用瘦身之旅。


🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解为什么要优化包体积以及它对业务的影响
  • ✅ 掌握鸿蒙安装包(HAP/APP)的构成与体积分布
  • ✅ 学会代码优化手段:Tree Shaking、混淆、压缩、按需引入
  • ✅ 掌握资源优化方法:图片压缩、WebP格式、资源删除、资源混淆
  • ✅ 学会图片优化三部曲:尺寸、格式、质量
  • ✅ 了解字体优化:子集化、按需加载
  • ✅ 理解按需加载与动态特性的使用场景
  • ✅ 掌握资源分包与按需下载策略
  • ✅ 能够在「民族图鉴」项目中实践完整的包体积优化
  • ✅ 能够解决优化过程中的常见问题

💡 需求分析

为什么要优化包体积?

1. 提升下载转化率

应用商店的数据显示:包体积每增加1MB,下载转化率就会下降一定比例。对于新用户来说,下载是第一道门槛。

影响因素

  • 网络环境:移动网络下,用户更不愿意下载大包
  • 等待时间:下载越久,用户放弃的概率越高
  • 心理预期:用户对不同类型的App有不同的体积预期

对于「民族图鉴」这类工具型应用,用户的心理预期可能是"几十MB以内"。如果超过这个预期,转化率就会明显下降。

2. 减少安装时间

包体积越大,安装时间越长。用户点击"安装"后等待越久,体验越差。尤其是在低端设备上,安装速度本身就慢,大包体会让这个问题雪上加霜。

3. 节省存储空间

手机存储空间是有限的。很多用户会定期清理不常用的App来释放空间。如果你的App体积大、使用频率又不高,很容易成为被清理的对象。

「民族图鉴」虽然不是高频应用,但如果体积足够小,用户更愿意保留它。

4. 降低分发成本

虽然这一点对开发者来说可能不那么直观,但包体积小确实能降低CDN带宽成本。对于用户量大的应用,这是一笔不小的开支。

鸿蒙安装包的构成

要优化包体积,首先要知道安装包里有什么。

鸿蒙应用的安装包主要有两种形式:

  • HAP(HarmonyOS Ability Package):模块级安装包
  • APP(Application Package):应用级安装包,包含多个HAP

一个典型的HAP包结构:

HAP包
├── ets/                    # 代码文件(编译后)
├── resources/              # 资源文件
│   ├── base/              # 默认资源
│   │   ├── element/       # 元素资源(字符串、颜色等)
│   │   ├── media/         # 媒体资源(图片等)
│   │   └── profile/       # 配置文件
│   ├── rawfile/           # 原始文件(保持原样)
│   ├── zh_CN/             # 中文资源
│   ├── en/                # 英文资源
│   └── dark/              # 深色模式资源
├── libs/                   # 原生库(.so文件)
├── module.json5           # 模块配置
└── pack.info              # 打包信息

体积占比通常是:

  • 资源文件:50%~80%(图片占大头)
  • 代码文件:10%~30%
  • 原生库:0%~20%(如果有C++库的话)
  • 其他:5%左右

对于「民族图鉴」这样的应用,图片资源(尤其是56个民族的封面图)很可能是体积最大的部分。

「民族图鉴」包体积现状分析

让我们看看「民族图鉴」的资源情况:

  1. 图片资源

    • 56个民族封面图(rawfile/coverImage/,每张约100-300KB)
    • 启动图标、Logo等(resources/base/media/)
    • 背景图、前景图等
  2. 代码文件

    • 页面组件(15个页面)
    • 服务层(8个服务)
    • 模型层、工具类等
  3. 资源文件

    • 多语言资源(中文、英文)
    • 深色模式资源
    • 字符串、颜色、尺寸等

粗略估算,如果56张封面图每张平均200KB,光是图片就有56 × 200KB ≈ 11MB。再加上代码和其他资源,整个App可能在15-20MB左右。对于工具类应用来说,这个体积不算小,但也不算大。不过,我们仍然有很大的优化空间。


🔧 核心实现

一、代码优化

代码优化是包体积优化的基础。虽然代码通常不是体积最大的部分,但优化代码不仅能减小包体积,还能提升运行时性能。

1. Tree Shaking(摇树优化)

Tree Shaking是一种通过静态分析移除未使用代码的技术。就像摇树一样,把枯叶(未使用的代码)摇掉,只保留有用的部分。

原理
基于ES Module的静态结构(import/export),在编译时分析哪些代码被使用了,哪些没有,然后将未使用的代码删除。

ArkUI中的Tree Shaking
ArkUI的构建工具默认开启了Tree Shaking,但我们需要遵循一些最佳实践才能让它发挥最大效果。

最佳实践

// ✅ 好:按需导入,只导入需要的部分
import { EthnicGroup } from '../models/EthnicModels';

// ❌ 不好:导入整个模块
import * as EthnicModels from '../models/EthnicModels';
// ✅ 好:使用具名导出,便于Tree Shaking分析
export function formatPopulation(pop: string): string {
  // ...
}

export function getRegionList(): string[] {
  // ...
}

// ❌ 不好:导出整个对象,无法单独移除
export default {
  formatPopulation,
  getRegionList
}

注意事项

  • 尽量使用ES Module的import/export语法
  • 避免副作用代码(模块顶层的函数调用、全局变量修改等)
  • 第三方库也要选择支持Tree Shaking的
2. 代码混淆

代码混淆是将代码中的类名、方法名、变量名等替换成短而无意义的名字,从而减小代码体积。

在「民族图鉴」项目中,已经有混淆配置文件(obfuscation-rules.txt),但在build-profile.json5中默认是关闭的:

{
  "name": "release",
  "arkOptions": {
    "obfuscation": {
      "ruleOptions": {
        "enable": false,  // 默认关闭
        "files": [
          "./obfuscation-rules.txt"
        ]
      }
    }
  }
}

开启混淆

将enable改为true即可开启混淆。但在此之前,我们需要配置好混淆规则,避免把不能混淆的代码也混淆了。

混淆规则配置(obfuscation-rules.txt)

# 开启属性混淆
-enable-property-obfuscation
# 开启顶层混淆
-enable-toplevel-obfuscation
# 开启文件名混淆
-enable-filename-obfuscation
# 开启导出混淆
-enable-export-obfuscation

# 移除console.log等调试代码
-remove-log

# 代码压缩(移除空格、换行等)
-compact

# 保留入口类名(根据实际情况配置)
-keep-global-name EntryAbility
-keep-global-name EntryBackupAbility

混淆的效果

  • 代码体积通常能减少20%~50%
  • 同时也有一定的代码保护作用(反编译更难)

注意事项

  • 反射调用的类名/方法名不能混淆
  • 序列化/反序列化的对象属性不能混淆
  • 与原生交互的接口不能混淆
  • 开启混淆后一定要充分测试
3. 代码压缩

代码压缩是指移除代码中的空白字符、注释、换行等,让代码变得更紧凑。

// 压缩前
function calculateTotal(items: Item[]): number {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total += items[i].price;
  }
  return total;
}

// 压缩后
function calculateTotal(a){let b=0;for(let c=0;c<a.length;c++)b+=a[c].price;return b}

在ArkUI中,release构建默认会进行代码压缩。我们也可以在混淆配置中通过-compact选项来启用。

4. 按需引入

按需引入是指只引入需要的模块,而不是整个库。

示例:工具函数按需引入

// ❌ 不好:引入整个lodash(假设存在)
import _ from 'lodash';

// ✅ 好:只引入需要的函数
import { debounce, throttle } from 'lodash';

在「民族图鉴」项目中,大部分代码都是自己写的,所以主要注意内部模块的按需引入即可。

「民族图鉴」实战:代码优化

让我们看看「民族图鉴」中可以优化的地方:

1. 开启混淆

修改build-profile.json5,在release构建中开启混淆:

{
  "name": "release",
  "arkOptions": {
    "obfuscation": {
      "ruleOptions": {
        "enable": true,
        "files": [
          "./obfuscation-rules.txt"
        ]
      }
    }
  }
}

2. 更新混淆规则

在obfuscation-rules.txt中添加必要的保留规则:

-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation
-remove-log
-compact

# 保留数据模型的属性名(避免序列化问题)
-keep-property-name EthnicGroup
-keep-property-name AIMessage
-keep-property-name MusicItem
-keep-property-name QuizQuestion
-keep-property-name UserProfile

# 保留枚举值
-keep-global-name AppLanguage
-keep-global-name TTSState

3. 检查import语句

确保所有import都是按需导入的,而不是导入整个模块。

二、资源优化

资源通常是包体积的"大头",尤其是图片资源。优化资源往往能带来最显著的体积 reduction。

1. 图片压缩

图片压缩是最直接、效果最明显的优化手段。

图片压缩的两个维度

  • 尺寸压缩:减小图片的像素尺寸(宽×高)
  • 质量压缩:在保持尺寸不变的前提下,降低图片质量

压缩工具推荐

  • 在线工具:TinyPNG、Squoosh
  • 本地工具:ImageOptim、XnConvert
  • 命令行:sharp、imagemin

压缩原则

  • 在可接受的视觉质量下,尽可能压缩
  • 不同类型的图片采用不同的压缩策略
  • 不要过度压缩,影响用户体验
2. 选择合适的图片格式

不同的图片格式有不同的特点,选择合适的格式能在相同质量下得到更小的体积。

常见格式对比

格式 透明度 动画 压缩率 适用场景
PNG 图标、透明背景图
JPEG 照片、复杂图片
WebP 很高 大多数场景
SVG - 矢量图标

WebP格式
WebP是Google推出的图片格式,支持有损和无损压缩,支持透明度和动画。在相同视觉质量下,WebP比JPEG小25%35%,比PNG小26%34%。

鸿蒙系统支持WebP格式,所以在「民族图鉴」中,我们可以考虑将民族封面图转换为WebP格式。

3. 图片优化三部曲

让我们总结一下图片优化的标准流程:

第一步:压缩尺寸

先问自己:这张图真的需要这么大吗?

  • 图标:按照设计规范,通常是24x24、32x32、48x48等
  • 列表缩略图:根据列表项大小决定,通常不超过400x400
  • 详情页大图:根据屏幕尺寸决定,通常不超过1080p

对于「民族图鉴」的56张民族封面图,我们需要确认:

  • 这些图片在哪些地方使用?
  • 最大显示尺寸是多少?
  • 当前图片尺寸是多少?有没有超大?

如果封面图原本是2000x2000的,但实际最大显示尺寸是400x400,那我们就可以把图片尺寸缩小到800x800(考虑2x屏),体积会大幅减小。

第二步:选择格式

根据图片内容选择最合适的格式:

  • 照片类(民族封面图):JPEG或WebP(有损)
  • 图标类(小图标、Logo):PNG或WebP(无损)
  • 矢量图:SVG(如果支持的话)

第三步:平衡质量与体积

找到"质量可接受、体积最小"的那个平衡点。

怎么找?可以做对比测试:

  • 用不同的质量参数导出图片
  • 对比视觉效果和文件大小
  • 选择质量下降不明显但体积下降明显的那个点

对于JPEG,通常质量参数在70-85之间是性价比最高的。
对于WebP,通常质量参数在60-80之间效果不错。

4. 删除未使用的资源

项目中经常会有一些不再使用的资源文件,比如旧版本的图片、测试用的资源等。这些"僵尸资源"白白占用着包体积。

怎么找未使用的资源?

  • 手动检查:搜索资源引用
  • 工具检查:使用构建工具的分析功能
  • 代码审查:定期清理

对于「民族图鉴」,我们可以检查:

  • resources/media/ 目录下的图片是否都被引用了
  • rawfile/ 目录下的文件是否都在使用
  • 不同语言/分辨率的资源是否有冗余
5. 资源混淆

资源混淆是将资源文件的路径和名字替换成短名字,从而减小资源索引表的体积。

比如:

  • resources/base/media/ic_search.pngr/a/b.png

资源混淆对资源本身的体积没有影响,但能减小资源索引表和代码中资源引用的体积。对于资源很多的应用,效果还是比较明显的。

在ArkUI中,可以通过构建配置来开启资源混淆。

「民族图鉴」实战:图片优化

「民族图鉴」的图片资源主要是56个民族的封面图,位于entry/src/main/resources/rawfile/coverImage/目录下。

优化步骤

1. 统一图片尺寸

先确认封面图的使用场景:

  • 详情页头部:宽度约为屏幕宽度,高度约200vp
  • 列表项:不使用封面图(用首字图标代替)

所以封面图的最大显示宽度大约是屏幕宽度(比如1080px)。我们可以将所有封面图的宽度统一调整为1080px(高度按比例缩放)。

2. 转换为WebP格式

将所有JPG封面图转换为WebP格式,质量设置为75左右。

预期效果:

  • JPG → WebP,体积减少约30%~50%
  • 56张图,每张平均从200KB降到100KB,总共减少约5.6MB

3. 重命名文件(可选)

当前文件名是01_han.jpg02_zhuang.jpg这样的格式。如果数量很多,可以考虑更短的命名,比如01.webp02.webp。不过对于56个文件来说,这个优化收益很小,可以不做。

三、字体优化

字体文件通常比较大,中文字体尤其如此。如果应用中使用了自定义字体,字体优化就很重要。

「民族图鉴」目前使用的是系统字体,所以这部分可能不是重点。但如果未来要添加自定义字体,就需要考虑优化。

1. 字体子集化

中文字体动辄几MB甚至几十MB,但我们实际用到的字可能只有几千个。字体子集化就是从完整字体中提取出我们需要的字符,生成一个更小的字体文件。

子集化工具

  • fontmin
  • 百度字体编辑器
  • 在线字体子集化工具

子集化的两种策略

  • 静态子集化:提前确定需要的字符,生成固定的子集字体
  • 动态子集化:根据用户输入动态加载需要的字体片段

对于「民族图鉴」,如果要使用自定义字体,可以提取所有民族名称、常用UI文字等字符,生成一个子集字体。

2. 按需加载字体

如果有多种字体(比如正文用一种,标题用一种),可以只加载当前页面需要的字体,而不是全部加载。

不过在鸿蒙应用中,字体通常是打包在安装包里的,所以按需加载更多是指"不把不用的字体打进包里"。

四、按需加载与动态特性

1. 什么是按需加载?

按需加载,顾名思义就是"需要的时候才加载"。传统方式是把所有代码和资源都打进安装包,用户安装后就能用所有功能。按需加载则是把非核心功能拆分出去,用户用到的时候才下载。

好处

  • 减小安装包体积
  • 用户按需下载,节省流量
  • 核心功能秒开,体验更好
2. 鸿蒙的动态特性

鸿蒙系统支持动态特性(Dynamic Feature),可以将某些功能模块做成动态HAP,用户需要时才下载安装。

适用场景

  • 使用频率低的功能(比如「民族图鉴」的AI聊天)
  • 体积大的功能(比如视频播放、AR功能)
  • 可选的高级功能(比如Pro版功能)

对于「民族图鉴」来说,可以考虑做成动态特性的功能:

  • AI聊天功能(依赖大模型,可能体积较大)
  • 音乐播放功能(如果有很多音乐资源的话)
  • 知识问答功能(如果数据量很大)

不过,动态特性也有缺点:

  • 用户使用需要等待下载
  • 实现复杂度更高
  • 需要服务器支持分发

所以是否使用动态特性,需要根据业务情况权衡。

3. 资源分包

资源分包是将资源按功能模块拆分,只下载当前需要的资源。

比如「民族图鉴」的56个民族封面图,如果用户只查看几个民族,理论上只需要下载这几个民族的图片就够了。但实际上,因为图片太多了,逐个下载反而可能影响体验。所以是否做资源分包,要根据具体情况判断。

资源分包的两种方式

  • 按模块分包:每个功能模块的资源单独打包
  • 按质量分包:高清/标清资源分开,根据设备情况下载

五、包体积分析工具的使用

在优化之前,我们需要知道包体积到底花在哪里了。工欲善其事,必先利其器。

1. 构建产物分析

最直接的方法就是看构建产物。

查看HAP包内容
HAP包本质上是一个ZIP文件,可以直接解压查看:

# 1. 找到构建产物
# entry/build/default/outputs/default/entry-default-signed.hap

# 2. 复制一份,改后缀为.zip
cp entry-default-signed.hap entry-default.zip

# 3. 解压
unzip entry-default.zip -d entry-content/

# 4. 查看各目录大小
du -sh entry-content/*

这样就能看到每个目录占用了多少空间,找到体积最大的部分。

2. DevEco Studio 构建分析

DevEco Studio 内置了构建分析功能。

使用方法

  1. 执行构建(Build → Build HAP(s)/APP(s))
  2. 打开构建产物目录
  3. 找到构建报告(build-report.html)
  4. 用浏览器打开,查看详细的体积分析

构建报告包含

  • 总体积统计
  • 各模块体积占比
  • 资源文件列表及大小
  • 代码体积分析
  • 体积变化趋势(和上一个版本对比)
3. 第三方工具

除了官方工具,还有一些第三方工具可以用来分析包体积:

工具 用途 特点
Android Studio APK Analyzer APK分析 功能强大,可视化好
7-Zip / WinRAR 压缩包查看 简单直接,看文件列表
du / TreeSize 目录大小统计 快速看哪些文件大
在线图片压缩工具 图片优化 TinyPNG、Squoosh等
4. 体积分析的步骤

标准分析流程

1. 构建Release包
   ↓
2. 解压,看总体积分布
   ↓
3. 找到最大的目录/文件类型
   ↓
4. 深入分析该类文件
   ↓
5. 制定优化计划

重点关注

  • 哪些资源文件最大?
  • 有没有重复的资源?
  • 图片格式是否最优?
  • 代码有没有冗余?
  • 有没有未使用的资源?
「民族图鉴」体积分析示例

假设我们对「民族图鉴」做一次体积分析,结果可能是这样的:

总大小:25MB
├── resources/rawfile/    12MB (48%) —— 56张封面图
├── resources/base/       3MB (12%)  —— 图标、背景图等
├── ets/                  5MB (20%)  —— 代码
├── libs/                 3MB (12%)  —— 原生库
└── 其他                   2MB (8%)   —— 配置文件等

结论很明显:图片是最大的优化点,占了将近一半的体积。优化图片,ROI最高。

💡 分析工具的价值
不要凭感觉猜哪里体积大,用工具测一下。
有时候你以为代码是大头,结果发现图片占了70%。
找到最大的那块,优化它,收益最大。


六、实战:「民族图鉴」56张封面图WebP转换全记录

理论说了这么多,让我们来一次实战:把「民族图鉴」的56张民族封面图从JPG转换成WebP,看看效果到底如何。

第一步:准备工作

原始文件信息

  • 位置:entry/src/main/resources/rawfile/coverImage/
  • 数量:56张
  • 格式:JPG
  • 命名:01_han.jpg, 02_zhuang.jpg … 56_hasake.jpg
  • 预估单张大小:150-300KB
  • 预估总体积:约12MB

工具选择

  • 在线工具:Squoosh(https://squoosh.app/)- 适合少量图片
  • 命令行:cwebp / sharp - 适合批量处理
  • 图形工具:XnConvert、ImageOptim - 适合可视化操作

这里我们用命令行方式,因为有56张图,批量处理效率高。

第二步:批量转换脚本

使用 Node.js + sharp 库做批量转换:

// 安装sharp
// npm install sharp --save-dev

// convert-to-webp.ts
import * as fs from 'fs';
import * as path from 'path';
import sharp from 'sharp';

const inputDir = './rawfile/coverImage/';
const outputDir = './rawfile/coverImage-webp/';
const quality = 75; // WebP质量参数

// 确保输出目录存在
if (!fs.existsSync(outputDir)) {
  fs.mkdirSync(outputDir, { recursive: true });
}

// 获取所有jpg文件
const files = fs.readdirSync(inputDir).filter(f => 
  f.endsWith('.jpg') || f.endsWith('.jpeg') || f.endsWith('.png')
);

console.log(`找到 ${files.length} 张图片,开始转换...`);

let totalOriginalSize = 0;
let totalWebpSize = 0;

// 批量转换
async function convertAll() {
  for (const file of files) {
    const inputPath = path.join(inputDir, file);
    const outputPath = path.join(outputDir, 
      file.replace(/\.(jpg|jpeg|png)$/i, '.webp')
    );
    
    // 原始文件大小
    const originalSize = fs.statSync(inputPath).size;
    totalOriginalSize += originalSize;
    
    // 转换
    await sharp(inputPath)
      .webp({ quality: quality })
      .toFile(outputPath);
    
    // 转换后文件大小
    const webpSize = fs.statSync(outputPath).size;
    totalWebpSize += webpSize;
    
    const ratio = ((1 - webpSize / originalSize) * 100).toFixed(1);
    console.log(`${file}: ${(originalSize/1024).toFixed(1)}KB → ${(webpSize/1024).toFixed(1)}KB (减少${ratio}%)`);
  }
  
  console.log('\n=== 转换完成 ===');
  console.log(`原始总大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
  console.log(`WebP总大小: ${(totalWebpSize / 1024 / 1024).toFixed(2)}MB`);
  console.log(`减少了: ${((1 - totalWebpSize / totalOriginalSize) * 100).toFixed(1)}%`);
}

convertAll();

运行脚本:

npx ts-node convert-to-webp.ts
第三步:查看转换结果

假设运行后的输出是这样的:

找到 56 张图片,开始转换...
01_han.jpg: 220.5KB → 105.2KB (减少52.3%)
02_zhuang.jpg: 185.3KB → 88.7KB (减少52.1%)
03_hui.jpg: 210.1KB → 98.3KB (减少53.2%)
...
56_hasake.jpg: 195.6KB → 92.1KB (减少52.9%)

=== 转换完成 ===
原始总大小: 11.25MB
WebP总大小: 5.38MB
减少了: 52.2%

效果分析

  • 56张图从11.25MB减少到5.38MB
  • 减少了5.87MB,约52.2%
  • 平均每张图从200KB降到96KB
  • 整体包体积从25MB降到约19MB,减少了24%

这个效果非常显著!而且是用很小的开发成本换来的。

第四步:质量检查

体积减下来了,但质量怎么样?会不会变得很模糊?

质量检查方法

  1. 肉眼对比:在手机上实际查看,看能不能看出区别
  2. 放大对比:把图片放大,看细节损失多少
  3. 盲测:找几个人来猜哪张是原图哪张是WebP,看能不能猜出来

建议的质量参数

  • 质量参数 75-80:大多数场景肉眼看不出区别
  • 质量参数 65-70:体积更小,细看有轻微差异
  • 质量参数 60以下:差异明显,不建议使用

对于「民族图鉴」的封面图,75的质量参数应该是比较合适的——体积减少一半,质量损失不明显。

第五步:代码适配

图片格式换了,代码里引用的地方也要改:

// 原来
const imagePath = `coverImage/${id}_${pinyin}.jpg`;

// 改成WebP
const imagePath = `coverImage/${id}_${pinyin}.webp`;

如果担心兼容性问题(比如要支持很老的系统版本),可以做降级处理:

// 先尝试加载WebP,失败则加载JPG
Image(this.webpSupported ? webpPath : jpgPath)
  .onError(() => {
    // WebP加载失败,降级到JPG
    this.currentImagePath = jpgPath;
  })

不过鸿蒙系统对WebP的支持很好,一般不需要降级。

第六步:验证效果

转换完成后,做一轮完整测试:

  1. 列表页图片加载是否正常
  2. 详情页大图显示是否正常
  3. 图片清晰度是否可接受
  4. 包体积是否确实减少了
  5. 有没有其他功能受影响

确认一切正常,优化就完成了!

💡 WebP转换的经验总结

  1. 先转几张试试看效果,再批量转
  2. 质量参数不是越低越好,找到平衡点
  3. 转换后一定要在真机上看效果
  4. 记得更新代码里的文件后缀
  5. 做好版本管理,万一要回滚也方便

七、实战:「民族图鉴」包体积优化全记录

让我们为「民族图鉴」制定一个完整的包体积优化方案。

第一步:分析现状

在优化之前,我们需要知道当前的体积分布:

  • 总共有多大?
  • 代码占多少?资源占多少?
  • 哪些资源最大?

可以通过构建分析工具来查看。

第二步:制定优化计划

根据现状分析,制定优化优先级:

优先级 优化项 预期收益 难度
图片压缩+WebP转换 30%~50%图片体积减少
开启代码混淆 20%~30%代码体积减少
图片尺寸优化 视情况而定
删除未使用资源 视情况而定
资源混淆 5%左右
动态特性 视拆分情况
第三步:具体实施

1. 图片优化

  • 检查56张封面图的尺寸,统一到合适大小
  • 将封面图转换为WebP格式,质量75
  • 检查其他图片资源,进行压缩优化

2. 代码优化

  • 在release构建中开启混淆
  • 配置混淆规则,保留必要的类名和属性名
  • 开启-remove-log移除调试代码
  • 开启-compact压缩代码

3. 资源清理

  • 检查所有资源文件,删除未使用的
  • 检查rawfile目录,确认所有文件都在使用

4. 构建配置优化

  • 开启资源混淆
  • 检查构建配置,确保所有优化开关都已开启
第四步:验证效果

优化完成后,对比优化前后的体积:

  • 总包体积减少了多少?
  • 各部分减少了多少?
  • 功能是否正常?(一定要充分测试)
预期效果

保守估计,经过以上优化,「民族图鉴」的包体积应该能减少30%以上。如果图片优化做得好,可能减少40%~50%。


📦 动态特性与按需加载

前面讲的都是静态的优化——把包做小。但有些功能,用户可能根本用不上,为什么要让他们下载呢?这就是动态特性和按需加载的思路。

什么是动态特性

简单说就是:把应用拆成多个模块,核心模块必须下载,非核心模块用户用到了再下载。

举个例子
「民族图鉴」有答题、音乐、AI聊天这些功能。有些用户只用来看民族介绍,从来不用答题、不听音乐、不跟AI聊天。那这些功能对他们来说就是浪费空间。

如果把答题、音乐、AI聊天做成动态特性:

  • 用户下载App时,只下载核心功能(首页、列表、详情)
  • 用户点进答题页时,提示"需要下载答题模块,是否下载?"
  • 用户同意后,再下载答题模块
  • 下载完了直接进入答题页

这样一来,初始安装包可以小很多。

动态特性的适用场景

不是所有功能都适合做动态特性。适合的场景:

适合做动态特性的

  • 使用频率低的功能(比如设置里的高级功能)
  • 体积大的功能(比如AR、3D模型)
  • 可选的功能(比如用户可以选择装不装)
  • 特定人群的功能(比如VIP专属功能)

不适合做动态特性的

  • 核心功能(用户一打开就要用的)
  • 使用频率高的功能
  • 启动时就需要的功能
  • 体积很小的功能(下载的时间都比省的空间不值)

对于「民族图鉴」来说:

  • 答题功能 → 可以考虑做动态特性
  • 音乐功能 → 可以考虑做动态特性
  • AI聊天功能 → 可以考虑做动态特性
  • 民族列表/详情 → 核心功能,不能做

动态特性的优缺点

优点

  • 初始安装包更小
  • 用户只下载需要的功能
  • 可以按需发布新功能
  • 灵活度高

缺点

  • 实现复杂度高
  • 用户体验有影响(要等下载)
  • 需要处理下载失败、网络不好等情况
  • 测试更复杂

所以要不要做动态特性,要根据实际情况权衡。对于「民族图鉴」这种体量的应用,可能暂时不需要。但了解这个思路是有好处的。

按需加载的其他思路

不一定非要用系统的动态特性能力,代码层面也可以做按需加载。

路由懒加载
页面级别的按需加载,进入这个页面时才加载对应的代码。

组件懒加载
不常用的组件,用到时再加载。比如弹窗里的复杂组件,默认不加载,弹窗打开时再加载。

图片懒加载
这个我们之前讲过,列表里的图片,滚动到可视区域时再加载。

数据懒加载
不是一进页面就把所有数据都拉下来,而是需要时再拉。比如详情页的评论,默认不加载,用户滑到评论区时再加载。

这些按需加载的思路,既能提升性能,也能减少初始加载的资源消耗。


🗂️ 资源分包策略

除了动态特性,资源分包也是减小初始包体积的常用手段。简单说就是:把资源分成好几份,核心资源打包在一起,其他资源单独存放。

为什么要做资源分包

场景
「民族图鉴」有56个民族的详情页,每个民族都有几张大图。全部加起来可能有几十MB。但用户一次只会看几个民族,不会56个全看完。

如果把所有图片都打在主包里:

  • 安装包很大
  • 用户下载慢
  • 很多图片用户可能根本不会看

如果做资源分包:

  • 主包里只放首页和列表需要的小图
  • 详情页的大图单独打包
  • 用户进入某个民族详情时,再下载对应民族的大图

这样初始包体积就小很多了。

资源分包的方式

按页面分
每个页面的资源单独打包。进入页面前下载对应资源。

按模块分
按功能模块打包,比如答题模块的资源、音乐模块的资源。

按质量分
低清图打在主包,高清图单独放。用户需要时再下载高清版。

「民族图鉴」的分包建议

  • 主包:封面小图、图标、公共资源
  • 详情大图包:56个民族的详情大图
  • 音乐资源包:各民族的背景音乐
  • 答题资源包:答题相关的图片和音频

资源分包的注意事项

  1. 加载时机:要提前预判用户需要什么,在用户用到前就开始加载

    • 比如用户在列表页浏览时,可以预加载前几个详情页的图片
    • 比如WiFi环境下,可以静默预加载更多资源
  2. 降级处理:资源加载失败怎么办?

    • 显示占位图
    • 提供重试按钮
    • 网络不好时提示用户
  3. 缓存策略:下载过的资源要缓存起来,不要每次都重新下载

    • 内存缓存
    • 磁盘缓存
    • 缓存有上限,定期清理
  4. 用户体验:不要让用户觉得"怎么老是在加载"

    • 加载要有进度提示
    • 能静默加载的就静默加载
    • 核心路径不要让用户等

💡 关于分包的建议
对于「民族图鉴」这样的应用,初期可以先不做资源分包。
先把静态优化做好(图片转WebP、压缩、代码混淆等)。
如果后续包体积还是太大,再考虑动态特性和资源分包。
不要上来就搞复杂的方案,简单方案能解决的问题就用简单方案。


✅ 包体积优化 Checklist

为了方便大家落地,我们整理了一份包体积优化Checklist。每次发布新版本前,可以对照检查一遍。

代码优化

  • 开启代码混淆(-enable-property-obfuscation)
  • 开启顶层混淆(-enable-toplevel-obfuscation)
  • 开启文件名混淆(-enable-filename-obfuscation)
  • 移除console.log等调试代码(-remove-log)
  • 开启代码压缩(-compact)
  • 按需引入,不导入整个模块
  • 使用具名导出,便于Tree Shaking
  • 避免副作用代码

图片优化

  • 所有图片已压缩
  • 照片类图片使用WebP格式
  • 图标类图片使用合适的尺寸
  • 图片尺寸不超过显示需要的2倍
  • 删除未使用的图片资源
  • 列表缩略图和详情大图分开存放
  • 考虑使用矢量图(SVG)代替位图

资源优化

  • 清理未使用的资源文件
  • 开启资源混淆
  • 多语言资源只保留需要的语种
  • 删除不需要的分辨率资源
  • 配置文件精简
  • rawfile目录下的文件都在使用

字体优化

  • 不使用自定义字体(使用系统字体)
  • 如果使用自定义字体,做了子集化
  • 字体文件已压缩

构建配置

  • Release构建开启了所有优化选项
  • 移除了debug相关的代码和资源
  • 没有打包测试代码
  • 原生库只包含需要的架构

监控与流程

  • 建立了包体积基线
  • 每个版本检查体积增长
  • 体积增长超过阈值需要评审
  • 定期做专项优化

「民族图鉴」当前状态对照

优化项 状态 预计收益
开启代码混淆 ⬜ 未做 减少20-30%代码体积
图片转WebP ⬜ 未做 减少30-50%图片体积
清理未使用资源 ⬜ 待检查 视情况而定
移除调试代码 ⬜ 未做 少量减少
资源混淆 ⬜ 未做 减少5%左右

建议优先完成前两项(代码混淆 + WebP转换),投入少、收益大。

💡 Checklist的使用方法

  1. 每次发布前对照检查
  2. 已完成的打勾,未完成的安排时间做
  3. 定期回顾,更新清单
  4. 把包体积纳入发布标准

❓ 常见问题

Q1:图片压缩后变模糊了怎么办?

这是最常见的问题。压缩和质量本身就是矛盾的,关键是找到平衡点。

解决方法

  1. 提高质量参数:如果JPEG质量70太模糊,试试80、85
  2. 换格式:有时候WebP在低质量下比JPEG更清晰
  3. 针对性压缩:不同图片用不同的质量参数,不要一刀切
  4. 检查尺寸:如果图片尺寸太大,先缩尺寸再压质量,效果更好

判断标准

  • 正常使用距离下看不出明显差异即可
  • 不要放大了100%去对比像素级差异,用户不会那么看
  • 可以做A/B测试,让用户判断是否可接受

Q2:混淆后应用崩溃了怎么办?

混淆后崩溃,通常是因为把不该混淆的东西混淆了。

常见原因

  1. 反射调用:通过字符串类名/方法名调用的,混淆后名字变了就找不到了
  2. 序列化/反序列化:JSON解析时用属性名匹配,混淆后属性名变了
  3. 系统API回调:某些系统接口要求特定的方法名

排查方法

  1. 查看崩溃日志,找到错误信息
  2. 根据错误信息定位是哪个类/方法出了问题
  3. 在混淆规则中添加keep规则,保留对应的类/方法/属性

预防措施

  • 开启混淆后充分测试,覆盖所有功能
  • 重要的数据模型类,保留属性名
  • 与原生交互的接口,保留类名和方法名

Q3:资源找不到了(Resources.NotFoundException)怎么办?

可能原因

  1. 资源被误删:清理未使用资源时删错了
  2. 资源混淆问题:混淆后引用路径不对
  3. 资源路径错误:代码中写死了资源路径

排查方法

  1. 确认资源文件是否存在
  2. 检查资源引用的写法是否正确
  3. 如果用了资源混淆,检查混淆规则是否正确

最佳实践

  • 使用$r()引用资源,不要硬编码路径
  • 删除资源前,先全局搜索确认没有引用
  • 资源混淆在测试环境充分验证后再上线

Q4:WebP格式兼容性怎么样?

鸿蒙系统是支持WebP格式的。但如果你要适配旧版本,需要确认最低支持的系统版本。

对于「民族图鉴」来说,如果只需要支持较新的鸿蒙版本,WebP是完全没问题的。

兼容性方案

  • 如果需要兼容不支持WebP的设备,可以在构建时生成多套格式
  • 或者就用JPEG/PNG,兼容性最好

Q5:为什么开启了Tree Shaking,体积没怎么变?

Tree Shaking的效果取决于代码结构。如果你的代码本身就没有多少未使用的部分,Tree Shaking的效果自然就不明显。

可能原因

  1. 未使用的代码本来就少:项目小,代码都用上了
  2. 写法不利于Tree Shaking:比如大量使用default export、副作用代码
  3. 第三方库:很多第三方库没有做Tree Shaking优化

提升Tree Shaking效果的方法

  • 使用具名导出(named export),而不是默认导出(default export)
  • 避免模块顶层的副作用代码
  • 选择支持Tree Shaking的第三方库
  • 按需引入,不要导入整个模块

Q6:包体积优化有什么"银弹"吗?

很遗憾,没有银弹。包体积优化是一项细致的工作,需要一项一项去抠。

但如果只选一个最重要的,那就是:图片优化

对于大多数应用来说,图片都是体积最大的部分。把图片优化好,通常就能获得最大的收益。

Q7:优化到什么程度才算够?

这取决于你的应用类型和目标用户。

参考标准

  • 工具类App:尽量控制在10MB以内
  • 内容类App:控制在50MB以内
  • 游戏/视频类:几百MB也正常

对于「民族图鉴」这类工具应用,目标应该是:

  • 基础版:10MB以内
  • 完整版:20MB以内

更重要的是建立监控机制,防止版本迭代过程中包体积"偷偷"增长。


📝 小结

本文全面介绍了鸿蒙应用包体积优化的各种方法。让我们总结一下:

核心知识点

  1. 为什么优化:下载转化率、安装时间、存储空间、分发成本
  2. 安装包构成:资源(50%80%)、代码(10%30%)、原生库、其他
  3. 代码优化:Tree Shaking、混淆、压缩、按需引入
  4. 资源优化:图片压缩、格式选择(WebP)、删除未使用、资源混淆
  5. 图片优化三部曲:压缩尺寸 → 选择格式 → 平衡质量与体积
  6. 字体优化:子集化、按需加载
  7. 按需加载:动态特性、资源分包
  8. 优化流程:分析现状 → 制定计划 → 实施优化 → 验证效果

「民族图鉴」优化实践

针对「民族图鉴」项目,我们提出的优化方案:

  • 最高优先级:56张封面图压缩+WebP转换(预计减少30%~50%图片体积)
  • 高优先级:开启代码混淆+remove-log(预计减少20%~30%代码体积)
  • 中优先级:图片尺寸优化、清理未使用资源
  • 低优先级:资源混淆、动态特性

优化的正确姿势

包体积优化不是一次性的工作,而是持续的过程:

  1. 建立基线:记录每个版本的包体积
  2. 监控增长:版本发布时检查体积增长是否合理
  3. 定期优化:每隔一段时间做一次专项优化
  4. 团队意识:让团队成员都有包体积意识

记住:包体积是一点点涨上去的,也要一点点抠下来。每优化100KB,都是在为用户体验做贡献。

下一篇,我们将探讨功耗优化——如何让你的应用更省电,减少后台耗电,让用户的手机更持久。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐