關於Flutter打包,你需要知道的基礎配置和包體積優化策略?

豆皮範兒發表於2021-03-23

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

相關文章