新 Flutter 项目跑起来之后,第一件让人挠头的事情往往不是写业务代码,而是把默认的com.example.xxx包名、默认的app_name和那一排千篇一律的应用图标换成自己的。这个问题看起来简单,实际操作起来却横跨 Android 和 iOS 两套工程体系,牵扯到 Manifest、Gradle、Xcode、资源目录、第三方平台后台,稍不留神就会漏改一处,在提审或者上架时暴露出来。这篇文章我把 App 名称、包名、应用图标这三件事从头到尾捋了一遍,每一步怎么改、为什么这么改、改完怎么验证,都写在下面。不管你是刚接触 Flutter 的新手,还是要准备上架发布的应用负责人,按这个顺序过一遍,基本不会被这些小问题卡住。
1. 动手前先理清楚:名称、包名、图标分别在管什么事
1.1 三条线分别定义在哪个文件
先说结论:Flutter 自己没有一套统一的配置入口,应用名称、包名、图标都是跑到各平台的原生工程里改的。这是很多新手第一次被绕晕的地方,以为在pubspec.yaml里改几行就能搞定,实际上pubspec.yaml只负责 Dart 层配置和资源声明,压根管不到系统桌面显示的信息。
从定义位置上看:
- 应用名称:Android 在
android/app/src/main/AndroidManifest.xml的android:label属性里;iOS 在ios/Runner/Info.plist的CFBundleDisplayName和CFBundleName字段里。 - 包名:Android 在
android/app/build.gradle的applicationId和namespace两个字段里,同时对应源码目录MainActivity.kt的 package 声明;iOS 在 Xcode 的 Runner target 配置,本质是project.pbxproj里的PRODUCT_BUNDLE_IDENTIFIER。 - 应用图标:Android 在
android/app/src/main/res/mipmap-*目录里,Android 8.0 以上还会走mipmap-anydpi-v26下的自适应图标配置;iOS 在ios/Runner/Assets.xcassets/AppIcon.appiconset目录下,由Contents.json管理尺寸清单。
说白了,这三条线在 Android 和 iOS 各有一套原生机制,虽然操作逻辑相似,但文件和字段完全不同。后面改动的时候,一定要把两个平台当成两个项目分别处理。
1.2 为什么“全局替换旧包名”这个思路不可行
很多人上来就 Ctrl+Shift+H 全局搜索com.example然后全部替换,结果编译报一堆错。原因在于:com.example这个旧包名在 Android 工程里至少出现在三个性质完全不同的地方。
namespace控制的是代码和资源生成的包路径,applicationId控制的是应用市场标识和系统签名身份,源码目录里的package声明控制的是 Kotlin/Java 类归属路径。这三个值虽然默认相同,但在 Gradle 里的职责是分开的。全局替换时如果不区分上下文,很可能把build.gradle里的namespace和applicationId改得不一致,或者把源码里的 import、Manifest 里的 package 属性、第三方 SDK 的文件路径统统误伤。
苹果这边就更麻烦,PRODUCT_BUNDLE_IDENTIFIER在project.pbxproj里会出现在 Debug、Release、Profile 等多个 build configuration 位置。用文本编辑器全局替换不是不行,但一旦改错一个字符或者漏掉一个配置项,真机签名的时候会弹出各种证书不匹配错误。所以我建议全部改完后再做一次全局搜索,确认没有任何遗漏,再用 Android Studio 或 Xcode 分别打开工程验证。
1.3 动手前的准备清单
工欲善其事,必先利其器。我习惯在动手之前把工具和素材先准备好,避免改到一半到处找东西:
- 一台装了 Android Studio 的电脑,用来处理 Android 工程并做 Gradle Sync。
- 一台装了 Xcode 的 Mac,用来处理 iOS 工程和真机签名,没有 Mac 的话 iOS 部分只能先在代码层面改,真机验证得上线前做。
- 一张 1024x1024 的 PNG 应用图标素材,主体不要贴边,四周留出至少 15% 的安全边距。
- Android Studio 或 VS Code 的全局搜索功能,用来排查旧包名残留。
- 可选:
flutter_launcher_icons插件,帮我们批量生成各尺寸图标。
有这些就可以开工了。下面按照“名称 → 图标 → 包名”的顺序写,因为名称和图标属于相对独立的改动,适合先做完并验证,包名牵扯最广,放到最后集中处理不容易遗漏。
2. 修改 App 名称:Android 与 iOS 分开操作
2.1 Android 名称修改:一个 label 属性背后的多语言问题
Android 的应用名称定义在AndroidManifest.xml里,找到<application>标签,默认大概是这样的:
<application android:label="my_app" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">直接把android:label改成你想要的中文名或英文名,保存后重新运行就能生效。但我更推荐把名称抽到字符串资源里,这样后续做多语言、多渠道、多 flavor 都方便。
在android/app/src/main/res/values/strings.xml里建一个app_name字符串:
<resources> <string name="app_name">我的应用</string> </resources>然后 Manifest 里改成:
<application android:label="@string/app_name" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">如果你要做多语言显示,在res/values-en/目录下也建一个strings.xml,里面放英文名:
<resources> <string name="app_name">My App</string> </resources>系统会根据用户语言自动选择对应名称。这里有个坑:如果你在依赖库或者其他模块的 Manifest 里也配置了android:label,合并 Manifest 时可能报冲突,提示 label 属性同时存在于多个文件。解决方法是在主工程的<application>标签上加上xmlns:tools和tools:replace="android:label",让主工程强制覆盖。
另一个我踩过的坑是:修改android:label后,如果只是hot restart,有些国产手机的桌面并不会立即刷新应用名,需要卸载重装才能看到效果。这不是代码问题,是系统桌面缓存了应用标签,重新安装一次就好。所以验证名称修改时,直接用flutter run --release或者卸载重装。
2.2 iOS 名称修改:CFBundleDisplayName 与 CFBundleName 的区别
iOS 这边要改两个字段:CFBundleDisplayName和CFBundleName。CFBundleDisplayName是系统主屏幕上显示的名字,CFBundleName是 App 内部的短名称。桌面图标下方的名字由CFBundleDisplayName决定,如果该项缺失或为空,系统会退而求其次用CFBundleName。所以最稳妥的做法是两个字段都设置成一致的名称。
打开ios/Runner/Info.plist,找到:
<key>CFBundleDisplayName</key> <string>My App</string> <key>CFBundleName</key> <string>my_app</string>把CFBundleDisplayName改成中文或者你想要的正式名称。CFBundleName建议保持一个简单的 ASCII 短名,因为它在某些系统接口和内部用途中会用到,包含中文容易引起不可预期的问题。不过我见过不少项目直接把中文塞进CFBundleName也能跑起来,只能说尽量不要。
如果想要和 Android 一样做多语言名称,可以在 Xcode 里为InfoPlist.strings创建多语言本地化文件。InfoPlist.strings的配置方式是:
"CFBundleDisplayName" = "应用显示名称";注意这个文件要放在对应的语言目录下,比如zh-Hans.lproj/InfoPlist.strings,并且要在 Xcode 里正确配置 Localization。如果只是随便添加一个文件而没有关联本地化,改动是不会生效的,这一点特别容易让新手困惑。最省心的做法还是直接在Info.plist里写好主名称,再考虑多语言补充。
2.3 名称修改后不生效的典型原因
名称改完不生效,我总结出三个高频原因。
第一是改了但没重新安装。模拟器和真机桌面都有应用图标缓存,尤其在 iOS 上特别顽固,我建议修改完名称后先完全退出 App,再重新 install,如果还不行就重启模拟器。
第二是改了错误的 Manifest。如果你的项目配置了productFlavors或buildTypes,某个 flavor 的 Manifest 可能通过 manifestPlaceholders 或者自己的AndroidManifest.xml覆盖了 label。比如src/debug/AndroidManifest.xml里定义了不同的 label,那么你在 main 里改得再对,跑 debug 包时看到的还是 debug 版名称。排查思路是先搞清楚当前跑的到底是哪个 variant。
第三是 iOS 的缓存问题。iOS 桌面图标名称缓存很有迷惑性,有时候改完 Info.plist,重新 build 后桌面还是旧名字,这时卸载 App 再安装通常就好了。如果卸载重装还不行,再检查是不是项目里有多份 Info.plist,比如某些第三方插件模板会附带自己的 plist。
3. 修改包名:最需要细心的一步
3.1 Android 包名:namespace 与 applicationId 双轨配置
新版本 Flutter 模板生成的android/app/build.gradle里,包名相关内容是这样的:
android { namespace = "com.example.my_app" compileSdk = flutter.compileSdkVersion ... defaultConfig { applicationId = "com.example.my_app" minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion ... } }namespace和applicationId是两个完全不同的概念。namespace决定 R 类和 BuildConfig 类生成的包路径,也决定了代码里import com.example.my_app.R这样的引用是否合法;applicationId是应用市场的唯一标识、系统识别应用的 ID,以及第三方平台绑定签名用的身份。大多数项目把这两个值保持一致,但技术上它们不必相同。
修改时建议这两个字段统一改,比如改成com.acme.mobile:
namespace = "com.acme.mobile" ... defaultConfig { applicationId = "com.acme.mobile" ... }如果你使用的是非常老的工程,build.gradle里可能没有namespace字段,而在AndroidManifest.xml的根节点上写了一个package="com.example.my_app"。这种情况需要把 Manifest 里的 package 属性删掉,然后在build.gradle里显式加上namespace,因为新版 Android Gradle 插件(AGP 8.0 以后)强制要求配置 namespace,否则会直接报错Namespace not specified。
修改完成后,在 Android Studio 里做一次 Gradle Sync,然后执行flutter clean再重新构建,避免旧构建缓存干扰。
3.2 Android 源码目录与 MainActivity 同步调整
改完namespace和applicationId还不够,源码目录里的MainActivity也要跟着搬家。
新模板里MainActivity一般在android/app/src/main/kotlin/com/example/my_app/MainActivity.kt,头部大概是:
package com.example.my_app import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity()你要把目录结构改成android/app/src/main/kotlin/com/acme/mobile/MainActivity.kt,同时把文件里的package声明改成package com.acme.mobile。如果老工程用的是java目录而不是kotlin,操作方式一模一样,只是路径前缀不同。
在 Android Studio 里直接拖拽文件移动是可以的,但我建议用右键菜单里的 Refactor,它会自动帮你扫描文件中的引用。移动完成后,再把全工程搜一遍com.example.my_app,翻翻有没有其他地方引用了旧包名,比如测试代码、proguard-rules.pro注释里写的包名、以及第三方 SDK 初始化时硬编码的包名字符串。
这里有个容易混淆的点:如果只修改build.gradle里的namespace而不移动MainActivity的 package 声明,在大多数情况下也能编译通过,因为 Kotlin 允许 package 声明与目录不一致。但这是一种非常不好的状态,后续维护的人很容易误以为源码还在旧路径下,而且一旦你开始使用依赖注入、源码生成这类工具,不一致的包路径就会立刻露出马脚。所以我建议彻底一点,目录、package 声明、build.gradle 三处统一改。
3.3 iOS 包名:Bundle Identifier 的无痛修改方式
iOS 的包名在 Flutter 工程里的标准称呼是Bundle Identifier。修改这个值,最稳妥的方式不是直接编辑project.pbxproj,而是用 Xcode 的界面操作:打开ios/Runner.xcworkspace,选中 Runner target,在 General 标签页找到 Identity 一栏,修改 Bundle Identifier 为新值。
Xcode 会把这个值同步写入project.pbxproj的 Debug、Release、Profile 三个配置项,省得手动改错。如果你手里只有一台 Windows 电脑没法跑 Xcode,也可以直接用文本编辑器打开ios/Runner.xcworkspace同一目录下的project.pbxproj,全局搜索旧包名,替换成新包名,通常会出现两次以上,注意全部替换干净。
有一个值得记住的细节:Info.plist里不应该出现硬编码的CFBundleIdentifier完整字符串。Xcode 是通过 build setting 里的PRODUCT_BUNDLE_IDENTIFIER来生成最终 bundle id 的,Info.plist里通常只写$(PRODUCT_BUNDLE_IDENTIFIER)占位符。如果你在 plist 里写死了旧包名,即使 build setting 改了,最终产物里仍然可能是旧值。这也是很多人改了 Xcode 配置但签名或后台始终认不出来的原因。
改完 iOS 包名后,如果项目用了 CocoaPods 管理插件,建议重新执行一次pod install,避免某些生成的配置文件里残留旧包名信息。然后flutter clean && flutter run重新构建。
3.4 第三方平台、签名与老包名的连带更新
包名改完之后,还有一批“外围配置”要跟着改,否则会出现极其隐蔽的运行时问题。我在实际项目中遇到过微信登录静默失败、支付宝回调没反应、Firebase 上报全是空数据等情况,最后排查下来都是包名没有同步更新。
微信开放平台:Android 侧需要在开放平台后台修改应用包名和签名(应用签名是 MD5 指纹,去掉冒号转成小写);iOS 侧需要同步修改 Bundle ID 和 Universal Links。如果你的应用已经发布,开放平台不允许随意修改包名,那就只能重新创建一个应用,代价很大。
支付宝开放平台:Android 后台配置的是应用包名和签名,iOS 是 Bundle ID。签名的包名组合一旦和登录时使用的包名不一致,支付和登录都会失败。
Firebase:Android 的google-services.json里有一个package_name字段,必须和applicationId完全一致,否则构建期就会报No matching client found for package name。iOS 的GoogleService-Info.plist里有个BUNDLE_ID,也要同步改。
推送服务(极光、友盟、个推等)各厂商后台都绑定了包名或 bundle id,改完客户端后逐个登录管理后台更新,不然推送通道注册时会失败。
另外建议把“旧包名”作为关键词在工程里再搜一遍,包括.dart_tool、build 目录之外的源码和配置文件。如果你看到android/app/google-services.json或ios/Runner/GoogleService-Info.plist还是旧包名,直接替换并重新下载配置文件。这个过程很繁琐,但漏一步都会在后期爆发问题。
4. 替换应用图标:一个素材吃遍所有平台
4.1 Android 图标文件结构与自适应图标
Android 应用图标存放在android/app/src/main/res/下面的多个mipmap目录里,Flutter 模板默认生成的是:
mipmap-mdpi:48x48mipmap-hdpi:72x72mipmap-xhdpi:96x96mipmap-xxhdpi:144x144mipmap-xxxhdpi:192x192
这些尺寸的换算关系是 dpi 倍数,mdpi 为基准 1x,hdpi 是 1.5x,xhdpi 是 2x,以此类推。最简单的做法就是准备一张 192x192 的高清图,按比例缩放到各个目录,直接覆盖ic_launcher.png。
但这里真正需要留神的是 Android 8.0 以上的自适应图标机制。Flutter 模板里通常会有一个mipmap-anydpi-v26/ic_launcher.xml文件,内容大概长这样:
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon>自适应图标把背景层和前景层分开,系统会根据不同桌面环境把图标裁成圆形、圆角矩形、方形等形状。前景层只有中间约 66/108 的区域是安全区,如果图案铺满整个图片,很容易在圆形桌面上被裁掉边缘内容。所以设计素材的时候,主体内容尽量放在中心位置,四周留白。
如果你不需要那么复杂,只需要替换 Android 图标,那覆盖mipmap-*下的ic_launcher.png和ic_launcher_round.png就行,同时把mipmap-anydpi-v26内的ic_launcher.xml也同步更新指向新的资源,否则 Android 8.0 以上设备会继续显示系统生成的默认图标。最简单省心的方式还是交给工具统一处理,下面会讲到。
4.2 iOS 图标文件结构与上架要求
iOS 的图标在ios/Runner/Assets.xcassets/AppIcon.appiconset目录下,由一个Contents.json和一堆 PNG 文件组成。新手看到里面一堆尺寸容易懵,其实核心要求就两条:第一是图片必须是 PNG 格式,第二是不能包含透明通道。
新版本 Xcode 支持单尺寸模式,Contents.json只需要声明一张 1024x1024 的图片,系统会按需自动缩放。比如这样可以:
{ "images" : [ { "filename" : "AppIcon-1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } }如果你打开现有的Contents.json,看到的是多尺寸列表,里面会包含 20、29、40、60、76、83.5 这些字号对应的 @2x/@3x 图,这是老版 Xcode 的格式。想省事的话,可以直接把所有尺寸项都指向同一张 1024 图片,Xcode 构建时通常也能接受。但最保险的做法还是用工具把所有尺寸生成齐全,避免上架校验时报错。
iOS 图标有个特别容易犯的错误:给图标加了圆角或者透明背景。iOS 系统会自动把图标裁成圆角矩形,如果你在图片里预先加了圆角,系统再裁一次,就会出现白边、黑边或者透明缺口。正确做法是提供一张填满整个画布、没有透明通道、不做圆角的方形图。这个细节在我第一次做上架打包时栽过跟头,提审时被 App Store Connect 明确拒绝。
4.3 用 flutter_launcher_icons 一键生成全套图标
手工抠尺寸很麻烦,所以我推荐直接使用flutter_launcher_icons插件。这个包在pubspec.yaml里加一行依赖,再写好配置就能批量生成 Android 和 iOS 两端所有图标。
在pubspec.yaml的dev_dependencies里添加:
dev_dependencies: flutter_launcher_icons: ^0.14.3然后在pubspec.yaml底部或者独立配置文件里声明图标源:
flutter_launcher_icons: android: true ios: true image_path: "assets/icons/app_icon.png" adaptive_icon_background: "#FFFFFF" adaptive_icon_foreground: "assets/icons/icon_foreground.png" remove_alpha_ios: true其中,image_path是基础图标源,Android 的普通图标和 iOS 的图标都会以它为准生成;adaptive_icon_background是自适应图标的背景颜色;adaptive_icon_foreground是自适应图标的前景层图片,建议准备一张主体居中且留足安全边距的图;remove_alpha_ios会在生成 iOS 图标时自动去除透明通道,这个选项强烈建议打开。
配置好之后,在项目根目录运行:
dart run flutter_launcher_icons如果你用的是比较老的 Flutter 版本,命令可能是:
flutter pub run flutter_launcher_icons这个工具会自动扫描 Android 工程,生成mipmap-*各目录下的图标文件,同时更新 iOS 的 AppIcon 资源集。我实测下来,Android 的自适应图标、旧版 PNG 图标、iOS 的单尺寸 1024 图标都可以一次处理好,比自己手工抠图省力太多。
有一点要注意:工具不会修改你在AndroidManifest.xml里引用图标路径之外的资源。如果你的 Manifest 中android:icon指向的不是@mipmap/ic_launcher,而是其他自定义 drawable,那么工具生成后依然不会生效,需要你自己把 Manifest 指向调回或手动替换目标资源。
4.4 图标替换后的验证与检查
图标替换后不能只看 IDE 预览,最好跑一个真机或模拟器包验证。
Android 端可以先flutter run安装到设备,看看桌面图标是否更新了。如果没变,检查是否安装了旧版本导致的缓存,先卸载再安装。也可以用adb shell dumpsys package 你的包名查看系统记录的 icon 引用,确认是不是指向了预期的资源位置。
iOS 端模拟器的图标缓存更顽固,我经常遇到改完图标重装模拟器上还显示旧图的情况。把模拟器里的 App 删掉,然后重置模拟器内容与设置,再重新 build 一次,基本就好了。如果要在真机上验证,直接重装新包即可。
上架前的最终检查也印证了那个老原则:一定基于 release 包检查,不要拿 debug 包做最终判断。某些配置在 debug 和 release 下可能是不同的,图标资源也有可能被 flavor 覆盖。
5. 常见问题与排查技巧实录
5.1 改了名称/图标却不显示时按这个顺序查
碰到改了没效果的问题,我建议按下面顺序排查:
第一,先确认你改的确实是当前构建产物会用到的资源。检查当前运行的 flavor、buildType,看对应模块的 Manifest 和资源目录有没有覆盖。比如 debug 的AndroidManifest.xml里如果单独设置了android:label,你改 main 里的 label 就不会生效。
第二,卸载重装。系统桌面有缓存,名称和图标这类资源在覆盖安装时尤其容易显示旧值。卸载不是清数据,而是真正删除应用,再重新安装。
第三,检查最终构建产物。Android 可以反编译 APK,查看AndroidManifest.xml里的 label 和 resources 列表;iOS 可以查看.app包里的Info.plist和AppIcon资源。如果产物里是对的但系统显示不对,那是缓存问题;如果产物里就是旧的,说明你改错了文件位置。
5.2 包名改完编译报错的定位方法
包名相关的编译报错通常集中在两类。
第一类报错是 Kotlin 文件找不到或者包名不匹配,典型提示类似:
e: file:///.../MainActivity.kt:3:1 Declaration with name 'MainActivity' doesn't match the package 'com.example.my_app'这种情况就是MainActivity.kt里的package声明和目录路径不一致。把文件移动到 namespace 对应的目录下,并同步修改 package 声明即可。
第二类报错是资源符号找不到,比如unresolved reference R或者Unable to load class com.example.my_app.BuildConfig。这通常是namespace没改,或者改了applicationId但忘了改namespace。R 类和 BuildConfig 属于 namespace 管,不归 applicationId 管,记住这句话可以少踩一半坑。
还有一些比较隐蔽的问题,发生在改完包名后执行flutter run却提示 Gradle 缓存异常。这种用三板斧解决:先flutter clean,再删除android/.gradle缓存目录,最后重新构建。如果还不行,检查是否有多个模块,每个模块的build.gradle里的 namespace 也要逐一确认。
5.3 第三方服务回调失灵的排查思路
包名改完后,第三方 SDK 出现回调不触发、登录失败、支付无响应的问题,十有八九是平台后台配置没同步。
排查的时候不要一上来就怀疑代码,先看平台后台:微信开放平台、支付宝开放平台、Firebase 控制台、各推送厂商后台都打开,确认包名或 Bundle ID 到底是多少。Android 平台还有个重点:微信登录和支付宝支付通常绑定的是“包名 + 签名”的组合,如果你改了包名但签名没变,这个组合在平台后台是不匹配的,因此必须同步在后台修改。签名获取方法很简单,使用官方提供的签名获取工具或者命令行 keytool 生成。
客户端这边也要检查是否有硬编码包名的地方。有人在初始化推送 SDK 时把packageName写死在代码里,改完包名忘了更新这份代码,服务端校验就会失败。全工程搜一遍旧包名,把出现在注释、字符串、配置项里的全部改成新值。
如果 iOS 侧还开了远程推送或关联域名,那么还要去 Apple Developer 后台新建对应的 App ID,更新配置文件描述文件,否则真机调试时签名阶段就会报错。
5.4 一些冷门但很实际的坑
这些坑不一定每次都遇到,但遇到就是大麻烦,我单独写出来提醒大家。
第一个是 Flutter 插件自带的 Android 资源。有些插件会把资源文件放到自己的android/src/main/res里,如果你的主工程里也用tools:replace和插件冲突,编译时会提示资源合并冲突。我的处理习惯是:凡是涉及<application>标签属性冲突的,先看看是哪个依赖库引起的,再针对性地加tools:replace,不要让主工程无脑全覆盖所有属性。
第二个是 iOS 的多环境配置。如果你用xcconfig文件管理不同环境的 Bundle ID,那么单纯的project.pbxproj修改可能只是动了某一个配置文件。要检查Configuration列表里的每一项,确保 Debug、Release 以及自定义 flavor 对应的一致性。
第三个是关于新项目创建的源头建议。以后创建新 Flutter 项目时,直接用flutter create --org com.yourcompany --project-name your_app .指定好组织名和项目名,这样生成的初始包名就是正式的,不需要再经历一次改包名的折腾。这个不起眼的参数帮我省去了大量重复劳动,强烈建议养成习惯。
第四个是改包名和上架时机的强绑定关系。Android 的applicationId和 iOS 的 Bundle ID 都相当于应用的身份证,应用上架后就不能再改了。Android 端硬改 applicationId 在系统层看待新包名就是一个全新的应用,iOS 端则会导致现有用户无法收到更新。所有包名调整必须在对外发布前完成,否则前后两个名字在市场上会被当成两个不同的 App。
最后再分享一个小技巧:整个改包名流程里,我最常用的操作不是 IDE 里的重命名功能,而是先全局搜索旧包名,把出现的文件列成一个清单,然后在清单上逐项勾销。看起来原始,但这种方式最不容易漏。尤其是在接了大量第三方 SDK 的项目里,旧包名经常藏在各种 json、plist、注释和远程配置文件里,一个不留意就是上线前的一处暗雷。把这些配置文件都过一遍,然后针对性地验证登录、支付、推送、统计这些核心模块,基本就能保证不会在包名相关的问题上翻车了。