Hi~ 豆皮粉們!最近在學什麼,變厲害了沒?今回就請大家來讀讀由位元組跳動的“saucxs” 精心製作的《Flutter打包的基礎配置和包體積優化策略》,漲漲跨端領域的知識,在你的前端“全棧”化的大路上走上一小步。
作者:saucxs | songEagle
來源:原創
前言
讀者朋友們,你們有沒有遇到過快樂地用 flutter 專案開發完,該上線了,但你對打包過程不熟悉,遇到大大小小問題的時候?來看看我這個過來人怎麼打包加優化的吧。
一、背景
在本地開發中,使用flutter run
命令還是 Android studio 執行或者除錯,flutter 構建的是 debug 版本,也就是本地除錯右上角出現 debug 標誌。
當本地除錯 OK 後,準備 release 版本,比如釋出到應用商城,或者交付使用者使用。
二、前期檢查工作
1、檢查AndroidManifest配置
檢視<app root>/android/app/src/main/
中的AndroidManifest.xml
檔案,並驗證這些屬性是否正確,特別是:
•application 屬性,這是應用的名稱。•uses-permission 屬性,啟用 flutter 工具和 app 應用進行通訊,預設是開啟,如果不開啟,直接刪除該屬性這一行。
2、檢視構建配置
檢視<app root>/android/app/build.gradle
,驗證這些屬性是否正確:
•defaultConfig 屬性•applicationId:制定始終唯一的 appid。•versionCode && versionName:app 應用版本號和版本號字串。•minSdkVersion && targetSdkVersion:指定最低的 API 級別以及應用程式設計運用的 API 級別。
3、app簽名
建立 keystore 。如果之前已經建立過 keystore,那就跳過本步驟直接看 4、應用中引入keystore
。
建立一個keystore,執行命令:
For more details, please visit https://support.apple.com/kb/HT208050.
yourMacBook-Pro:~ username$ keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
複製程式碼
生成的檔案是 key.jks 預設檔案地址:/Users/<你電腦名稱>/key.jks
注意:金鑰是私密檔案,不要加到git中。
下面是詳細的執行過程
Last login: Mon Nov 2 14:17:41 on ttys005
The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
yourMacBook-Pro:~ username$ keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
輸入金鑰庫口令:
再次輸入新口令:
它們不匹配。請重試
輸入金鑰庫口令:
再次輸入新口令:
您的名字與姓氏是什麼?
[Unknown]: C
您的組織單位名稱是什麼?
[Unknown]: byte
您的組織名稱是什麼?
[Unknown]: byte
您所在的城市或區域名稱是什麼?
[Unknown]: nj
您所在的省/市/自治區名稱是什麼?
[Unknown]: nj
該單位的雙字母國家/地區程式碼是什麼?
[Unknown]: cn
CN=C, OU=byte, O=byte, L=nj, ST=nj, C=cn是否正確?
[否]: y
正在為以下物件生成 2,048 位RSA金鑰對和自簽名證照 (SHA256withRSA) (有效期為 10,000 天):
CN=C, OU=byte, O=byte, L=nj, ST=nj, C=cn
輸入 <key> 的金鑰口令
(如果和金鑰庫口令相同, 按回車):
再次輸入新口令:
[正在儲存/Users/username/key.jks]
Warning:
JKS 金鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore /Users/username/key.jks -destkeystore /Users/username/key.jks -deststoretype pkcs12" 遷移到行業標準格式 PKCS12。
yourMacBook-Pro:~ username$
複製程式碼
4、應用中引入keystore
在<app dir>/android
下新建 key.properties 的檔案,其中包含對金鑰庫的引用:
storePassword=<你輸入的密碼>
keyPassword=<你輸入的密碼>
keyAlias=key
storeFile=/Users/<你電腦名稱>/key.jks
複製程式碼
5、在構建配置中新增簽名
對<app dir>/android/app/build.gradle
檔案做修改:
// ...
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
// 新增的內容
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion 28
// ...
// ...
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutter_tester"
minSdkVersion 18
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
// 新增內容 這塊就是從
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
// 新增內容
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
}
}
複製程式碼
現在應用打包的 release 版本將自動進行簽名。
三、打包命令
然後執行打包命令,構建釋出版(release)APK。如果您完成了前一節中的簽名步驟,則會對APK進行簽名。
使用命令列:
cd <app dir> (<app dir> 為您的工程目錄).
執行flutter build apk (flutter build 預設會包含 --release選項).
複製程式碼
打包好的釋出APK位於<app dir>/build/app/outputs/apk/app-release.apk
。
四、包體積優化策略
步驟三執行完,打包的這個過程就結束了,但為了追求極致,我們會發現打包之後體積有點大。
下一步,我們來分析一下這個 APK 包的構成,我們主要來看不混淆的情況下。
一般靜態資原始檔佔有大量記憶體,我們發現圖片在 apk 佔比為 80% + 0% = 80%
•Flutter 引用資源(assets 資料夾) 80% •Android 啟動頁背景和 app 的 logo(res 資料夾) 0%
主要是因為用了大量的圖片,並且沒有設定 Android 啟動頁背景和 app 的 logo。
(一)圖片和程式碼優化
1、處理圖片資源使用外部服務
與其將影像資產捆綁在應用程式內,不如將影像託管在諸如 firebase 之類的外部服務上,並使用包cached_network_image
在應用程式內呼叫這些影像。在首次啟動應用程式時,程式包會從提供的URL 中獲取影像並對其進行快取,因此您在隨後使用該應用程式時會得到一個快取的影像,因此這些影像不佔用下載空間,因為它們沒有捆綁在應用程式內。
2、壓縮 png 和 jpg
如果有的圖片不想從外部主機獲取,必須從本地獲取,所用的圖片必須壓縮 png 和 jpg,因為高質量的 png 和 jpg 會佔用應用程式的大小。
3、使用 svg 格式圖示
我們儘量使用向量圖 svg,而不是使用 png,因為 svg 可以相容不同的 dpi 裝置,並且能減少 apk 大小。
4、去掉未使用的包
在 pubspec.yaml 檔案中不需要或者根本沒有使用的庫或者包。
5、特殊字型使用 http 快取方式
字型也是程式大小的一個原因,當 UX 需要使用特定的字型,我們可以不用把字型檔案,比如 *.ttf
或者 .otf
檔案儲存在應用程式中,然後對映到pubspec.yaml檔案中,我們可以通過http獲取一次,並快取到應用程式的檔案系統中。
6、使用 proguard 優化器
proguard 是 java 的優化器,優化器不會改變表現形式並且使用更緊湊的方式優化程式碼。proguard 混淆原始名稱無所謂的型別,欄位,方法名稱,將長命名字串替換為短字串,比如換成a,b,c 以提高效率。然而包和類的名稱可能很長,但是不會影響效率。
我們在<app dir> /android/app/build.gradle
中的構建型別與以下所示類似:
buildTypes {
release {
minifyEnabled true //新增proguard
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //新增proguard
signingConfig signingConfigs.release //這是釋出的預設值
}
}
複製程式碼
在同一目錄中,建立檔案proguard-rules.pro並新增以下程式碼:
## Flutter wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
# -keep class com.google.firebase.** { *; } // uncomment this if you are using firebase in the project
-dontwarn io.flutter.embedding.**
-ignorewarnings
複製程式碼
最後我們在 gradle.properties 檔案中新增:
extra-gen-snapshot-options=--obfuscate
複製程式碼
7、shrinkResources 去掉無用程式碼
在 build.gradle 中
buildTypes {
release {
minifyEnabled true // added previously
shrinkResources true // add this
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // added previously
signingConfig signingConfigs.release // added previously
}
}
複製程式碼
PS:使用 shrinkResource 和 proguard 優化器,優化後,體積減少了0.1MB。
從147.0MB減少到146.9MB,減少的太少了。
(二)so優化
針對 Flutter 打出的 Apk 包,排在第二位的是lib,28.7MB,佔19.7%
將我們編寫的 Dart 程式碼轉化為不同架構下的 so 庫,以供原生呼叫。
針對不同 CPU 架構所代表含義:
•x86_64:Intel 64 位,一般用於平板或者模擬器,支援 x86 以及 x86_64 CPU 架構裝置。•arm64-v8a:第 8 代 64 位,包含 AArch32、AArch64 兩個執行狀態,且對應 32 、64 bit,並且支援 armeabi、armeabi-v7a 以及 arm64-v8a。•armeabi-v7a:第 7 代 arm v7,使用硬體浮點運算,具有高階擴充功能,相容 armeabi 以及 armeabi-v7a,而且目前大部分手機都是這個架構。
我們通過以下不同命令分別打包,構建制定的CPU架構的APK包,
flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi
複製程式碼
我們來解釋一下這個:
•flutter build apk 表示當前構建 release 包。•後面 android-arm,andriod-arm64,andriod-x64 則表示生成制定的架構的 release 包。•最後--split-per-abi 則表示按照不同架構分別打包,如果移除就包含所有 CPU 架構的 apk 包。
看看 app-armeabi-v7a-release.apk 包大小 126.2MB,爽啊,由 146.9 MB 直接減少到 126.2 MB。
我們看下對應的 apk 內容:
lib 佔比也從原來的 19.7%,28.7 MB 直接減少為 6.4%,8 MB。
(三)混淆優化
Flutter 也為我們提供了混淆命令:
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>
複製程式碼
簡單說明一下:
•--obfuscate:開啟混淆開關。•--split-debug-info:混淆生成的map符號表快取到此位置。
我們先測試一下,構建完整的apk的大小。
執行
flutter build apk --obfuscate --split-debug-info=splitMap
複製程式碼
大小從 146.9MB 降到 145.9MB,減少了1MB。
在專案根目錄下生成了符號檔案:
我們再試一下,直接針對不同 CPU 生成對應的 Apk 並新增混淆結果。
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory> --target-platform android-arm,android-arm64,android-x64 --split-per-abi
複製程式碼
我們執行:
flutter build apk --obfuscate --split-debug-info=splitMap --target-platform android-arm,android-arm64,android-x64 --split-per-abi
複製程式碼
未混淆的 v7a 大小與開啟混淆相比,126.2MB 減少到 125.9MB 。開啟混淆減少了 0.3 MB。
覺得差別不大。
五、總結
一旦打包的基礎配置完事後,基本上不用怎麼改,優化配置完事後,需要一個優化的打包命令。
覺得這是一個比較有用的打包命令:
flutter build apk --obfuscate --split-debug-info=splitMap --target-platform android-arm,android-arm64,android-x64 --split-per-abi
複製程式碼
•不同CPU結構分別打包 •要混淆,生產的字串Map在splitMap資料夾中
The End