一、前言
在應用RN開發(fā)跨平臺APP階段,從git中拉取項目,應用Jenkins進行組包時,發(fā)現(xiàn)最終生成的ipa安裝包版本號始終與項目中設置的版本號不一致。
二、問題描述
經(jīng)過仔細排查,發(fā)現(xiàn)Jenkins在Archive
編譯、歸檔階段失敗,但是后續(xù)Export
階段生成了ipa包。
error: Multiple commands produce '/Users/xxx/Library/Developer/Xcode/DerivedData/xxx-eomylkmdzkgaughihoblturddotc/Build/Products/Debug-iphonesimulator/PopNews.app':
1) Target 'xxx' has create directory command with output '/Users/xxx/Library/Developer/Xcode/DerivedData/xxx-eomylkmdzkgaughihoblturddotc/Build/Products/Debug-iphonesimulator/PopNews.app'
2) That command depends on command in Target 'xxx': script phase “[CP] Copy Pods Resources”
三、問題解決
-
在xCode中打開應用,選中項目 target -> Build phase -> Copy Pods Resources -> Output Files -> 移除
${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH} /*
。 -
將項目源碼壓縮上傳至Jenkins預設指定目錄,進行流水線組包作業(yè)。
四、拓展閱讀
4.1 版本號查看
android
中 android/app/build.gradle
文件中版本設置如下:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.china.shq5785"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 18072801
versionName "2.2.5"
multiDexEnabled true
testBuildType System.getProperty('testBuildType', 'debug')
// This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
ndk {
//設置支持的SO庫架構
abiFilters "armeabi", "armeabi-v7a", "x86_64" //, "arm64-v8a"
}
missingDimensionStrategy 'react-native-camera', 'general'
}
......
}
ios
在配置文件ios/mrcs.xcodeproj/project.pbxproj
中,可查看到如下配置信息:
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA6AA411A14368FB4EEC0CD3 /* Pods-mrcs.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = U4ALRF5A38;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/shq5785",
"$(PROJECT_DIR)",
);
GCC_PREFIX_HEADER = shq5785/PrefixHeader.pch;
GCC_WARN_ABOUT_RETURN_TYPE = NO;
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = shq5785/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/shq5785",
);
MARKETING_VERSION = 2.2.5;
OTHER_CODE_SIGN_FLAGS = "--deep";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.china.shq5785;
PRODUCT_NAME = shq5785;
PROVISIONING_PROFILE_SPECIFIER = "1111";
SWIFT_OBJC_BRIDGING_HEADER = "$(PRODUCT_MODULE_NAME)/shq5785-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
4.2 ipa包生成過程
iOS 開發(fā)的最后一步就是進行 App 的打包和分發(fā),這里分為兩個步驟:
-
Archive
:對Target
進行編譯、歸檔,生成.xcarchive
文件。 -
Export
:對.xcarchive
歸檔文件進一步處理,生成不同渠道的.ipa
包,進行分發(fā)。
當我們在 Xcode 菜單中選擇 Product -> Archive
后,編譯系統(tǒng)就會對當前的 Xcode 工程進行分析、編譯和打包,最終生成目標 Target 的一個 Archive(歸檔),我們可以在 Window -> Organizer -> Archives
頁面查看到所有緩存的歷史歸檔信息:
所謂的”歸檔“,就是對源碼進行編譯后,將此次編譯生成的各種文件、資源、記錄統(tǒng)一封裝到一個地方,方便進行管理和回溯。
右鍵選擇一個歸檔文件 archive,然后點擊 Show in Finder,可以看到它在 Finder 中表示為一個 .xcarchive
后綴的文件。
這個 .xcarchive
文件包含了應用和它的符號表信息(symbol information
)以及其它的相關資源,右鍵選擇顯示包內容,可以查看一個 Archive 歸檔中具體的文件結構:
其中每個文件夾的含義:
-
BCSymbolMaps
Xcode 對BitCode
符號表進行混淆(Symbol Hiding
)后生成的對照表,和dSYM
文件會一一對應。 -
dSYMs
存儲此次編譯的符號表(debug symbols
),用來符號化解析崩潰堆棧。 -
Products
存儲此次編譯生成的的 App 包(.app
)。
要注意的是這個包雖然包括了 App 運行需要的可執(zhí)行文件以及其它資源,但是和最終用戶下載的版本會有所不同。后續(xù)的 export
操作會對其進行進一步處理。
-
SCMBlueprint
如果 Xcode 打開了版本管理(Preferences -> Source Control -> Enable Source Control
),SCMBlueprint
文件夾會存儲此次編譯的版本控制信息,包括使用的 git 版本、倉庫、分支等。
如果未來想要回溯此次編譯的源碼版本,可以從這個 SCMBlueprint
中找到必要的信息。
-
SwiftSupport
如果在 Target 的Build Settings
中打開了ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES
,此次編譯使用的 Swift 版本對應的標準庫文件(.dylib
)會被放到這個文件夾中。
發(fā)布 App 時,這些標準庫也會被復制到 ipa bundle 中。
不過現(xiàn)在 Swift 的 ABI 已經(jīng)穩(wěn)定了,Xcode 10.2 及以后的版本打出來的包,在 iOS 12.2 及以后的系統(tǒng)的 app bundle 中不用再自帶鏈接庫了,節(jié)省了一定的體積。
了解 ipa 文件
.ipa(iOS App Store Package
) 文件是最終被安裝到 iPhone 上的應用格式,包含了運行 App 所必需的的簽名、二進制包、資源等內容。
在 Organizer 中無論用什么方式 export
應用的安裝包,最終生成的都是一個 .ipa
文件。
.ipa
本身是壓縮包文件,如果要查看 ipa 中的內容,可以右鍵查看包內容,觀察解壓以后的包,主要包含以下內容:
App 的簽名信息會被放到 _CodeSignature
文件夾中。
-
info.plist
存儲 App 主要信息的 plist 文件也會被一并打包到 ipa 中。 -
entitlements
entitlement 直譯成中文是“權益”、“權限”的意思。
當你在 Capabilities
中開啟一些特定的權限時,Xcode 會自動給你生成一個.entitlements
文件,在這個文件中通過 xml 的格式將這些授權記錄下來。
App 瘦身
要對 App 安裝包體積進行壓縮,首先要知道安裝包占用的多少空間,這些空間由哪些部分組成,然后再進行針對性的優(yōu)化。
查看最終用戶安裝包大小
實際上在 Xcode 本地 archive
出來的 app 包或者 export
出來的 ipa 包和最終用戶下載的版本會有所不同(通常體積會大很多)。因為蘋果可能會對 App 進行重新編譯(如果上傳了 BitCode
),也會針對不同的設備型號、iOS 版本分發(fā)不同的資源(比如 2x、3x 的圖片),最后還會對整個 .ipa
進行壓縮,以減少從 App Store 下載時耗費的流量。
那么如何估算用戶最終下載版本的包體積大小呢?其實在 iTunes Connect 頁面可以直接查詢到。
打開 iTunes Connect,選擇 我的App -> 活動 -> 所有構建版本,然后選擇一個要查看的版本:
找到 App Store 文件大小按鈕:
在彈出的列表中,可以看到在最新版本的 iOS 系統(tǒng)下,不同設備下載的包體積大?。?/p>
列表中的兩列:
下載大?。罕硎就ㄟ^無線下載的壓縮 App 大?。?/p>
安裝大?。喊惭b后此 App 將在用戶設備上占用的磁盤空間大??;
如何分析 App 包 Size?
為了更直觀地查看哪些資源占用了 App 安裝包的體積,我們可以借助一些文件工具來分析解壓后的 ipa 包,比如說 derlien
可以很直觀地看到各種不同類型文件所占的比例。
檢查未使用資源
隨著 App 的不斷迭代,我們往往會無意間引入很多用不到的資源,或者一些資源的引用已經(jīng)從代碼中去除了,但是沒有及時從 bundle
中刪除,造成 App 包體積的浪費。
為了查找這些不再使用的資源,可以借助開源工具 LSUnusedResources 來檢測整個工程。
LSUnusedResources 應用過程如下:
可以從下面的地址下載 LSUnusedResources
源碼,然后進行編譯…將源碼在Mac上運行,可以看到如下界面:
在Project> Path目錄中,點擊Browse…選擇要檢測工程的根目錄,然后點擊Search,開始進行檢索…,你可以在下方的日志窗中看到檢測結果>
檢測完成后,可以點擊Export將此日志導出,然后開始進行清理工作.切勿不管三七二十一直接開刪,畢竟是機器檢測,不可完全信賴。
針對一些特殊情況,比如代碼中使用例如 [UIImage imageNamed:[NSString stringWithFormat:@"icon_tag_%d", index]]
的方式引用資源,LSUnusedResources 也支持使用正則表達式來模糊匹配。
壓縮圖片
圖片文件是安裝包中最常見的資源了,常常會占有相當一部分比例,未壓縮的圖片體積往往相當大,通過一些工具壓縮圖片資源,節(jié)省空間:
-
無損壓縮:ImageOptim
-
有損壓縮:tinypng
使用 Asset Catalogs 存儲資源
相比于直接將圖片拖入工程目錄的方式,使用 Asset Catalogs 會更節(jié)省體積。Asset Catalogs 會用一個高度優(yōu)化的特殊格式來存所有圖片,對 png 圖片也會進行最大化的壓縮。
Xcode 工程模板會自動生成一個 Assets.xcassets
文件,我們也可以按需創(chuàng)建另外的 .xcassets
,最終在 ipa 包中,這些 xcassets 都會被壓縮到 Assets.car
文件中,一定程度上也保證了安全性。
除了圖片資源外,Asset Catalogs 也可以存儲文本、Data 甚至 AR、apple TV 相關的資源,非常全能,所以比較好的實踐就是:
能用 Asset Catalogs 管理的資源,盡量使用 Asset Catalogs 來管理
分析 LinkMap 文件
上面提到,App 包占用空間中很大一部分比例是最終編譯生成的可執(zhí)行文件(MACH-O
),可執(zhí)行文件的大小不僅和代碼體積有關,也受編譯器版本、編譯選項、鏈接庫、目標架構等影響。
可以通過分析編譯時產(chǎn)生的 LinkMap 來了解 MACH-O
文件的組成部分。
要找到對應的 LinkMap,首先在 Xcode Target -> Build Settings -> Write Link Map File
設置為 YES,然后在 Target -> Build Settings -> Path to Link Map File
選項中設置好 LinkMap 的生成地址(一般用 build 文件夾中的默認地址就好了),archive 成功后,我們就可以在對應地址找到該次編譯的 LinkMap 了:
LinkMap 記錄了編譯時的鏈接信息,用來描述可執(zhí)行文件的構造成分,包括代碼段__TEXT
和數(shù)據(jù)段 __DATA
的分布情況:
網(wǎng)上有很多腳本可以對 LinkMap 進行分析統(tǒng)計,比如:
可視化工具
-
js腳本
-
命令行工具
獲取到分析結果后,可以精確了解各個模塊、鏈接庫、方法在可執(zhí)行文件中的位置和占用空間:
對于一些占比特別大的模塊,常見的優(yōu)化思路有:
尋找可替代的,小體積的依賴庫,或者自己實現(xiàn)
去掉靜態(tài)庫中不需要的指令集,比如 armv7s,x86等,只保留發(fā)布需要的 armv7,arm64
提高代碼重用性
進一步分析代碼中沒有被使用的方法、模塊,對代碼庫進行精簡。
使用 bitcodebitcode
是在 LLVM 體系中介于前端語言(OC、Swift、C)和后端語言(X86、ARM的機器碼)之間的中間語言。
一次完整的編譯(從源碼到.O
目標文件)包含三個主要步驟:
前端(
Frontend
):負責把各種類型的源代碼編譯為bitcode
中間碼表示。優(yōu)化(
Optimizer
):負責對bitcode
進行各種類型的優(yōu)化,將bitcode
代碼進行一些邏輯等價的轉換,使得代碼的執(zhí)行效率更高,體積更小。后端(
Backend
):也叫CodeGenerator
,負責把優(yōu)化后的bitcode
編譯為指定目標架構的機器碼,比如 x86、arm64 等等。
可以在 Xcode Target -> Build Settings -> Enable Bitcode 中打開 bitcode 選項,這樣在 archive 時,會將中間生成的 bitcode
嵌入到鏈接后的二進制文件(.o
)中,用于提交到 App Store。
上面提到,bitcode
作為 LLVM 的中間語言,是可以從它直接編譯出最終程序的,Apple 拿到我們上傳的 bitcode 后,會使用最新的技術、編譯器針對不同的終端設備重新編譯 App,而這些重新編譯的版本往往比本地 Xcode 編譯的版本體積更小、效率更高。
如果后續(xù)需要支持新的平臺或者有新的編譯技術革新,蘋果就不用依賴開發(fā)者重新上傳了,直接使用現(xiàn)成的 bitcode
編譯出新的版本。
值得注意的是:在打包時,如果一些三方的依賴庫沒有開啟 bitcode
,或者開啟了但是沒有在最終引用的鏈接庫中帶有 bitcode
,那么整個工程就無法用 bitcode
來編譯了。
按需加載資源(On-Demand Resources)
iOS9 以后,蘋果提供了 On-Demand Resources
功能來減少安裝包的體積??梢詫⒁恍┵Y源標記為 “按需加載”,在需要使用的時候請求操作系統(tǒng)從 App Store 中下載。這個功能非常適合一些大型游戲、帶有付費內容或者大量不常使用的多媒體資源的 App。
當然,按需加載只是針對 App 使用的資源文件,不包括二進制可執(zhí)行文件或者源碼。
On-Demand Resources 的配置可以很輕松地在 Xcode 中完成。
首先在 Target -> Resource Tags 中創(chuàng)建資源 tag,一個 tag 表示一組可以被獨立下載的資源,后面我們就會使用這個 tag 在程序中請求操作系統(tǒng)下載對應的資源包到本地。
不同的 tag 包含的資源是可以重復的,App Store 會自己 differ,不會重復下載。
然后找到想要按需加載的資源文件,為它們分配一個或多個之前創(chuàng)建的 tag。
最后在代碼中,可以使用 NSBundleResourceRequest
:
請求下載
on-demand
資源;將資源標記為已使用狀態(tài)(這樣下載的資源會被清理掉,節(jié)省本地空間);
管理資源下載過程,配置優(yōu)先級、追蹤下載進度等等;
檢測磁盤容量警告;
下面的代碼是一個簡單的資源下載請求:
// 配置要下載的 tags
NSSet *tags = [NSSet setWithObjects: @"birds", @"bridge", @"city"];// 創(chuàng)建 NSBundleResourceRequest 對象
resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];// 請求資源,處理回調
[resourceRequest beginAccessingResourcesWithCompletionHandler: ^(NSError * __nullable error) {if (error) {// 處理錯誤self.resourcesLoaded = NO;return;}// 下載成功,可以直接使用這些資源了self.resourcesAvailable = YES;}
];
下圖總結了一個 on-demand
資源的生命周期:
文章來源:http://www.zghlxwxcb.cn/news/detail-687218.html
題外話:蘋果取消了移動網(wǎng)絡下載 150M 的限制,說明隨著手機容量的增加和移動網(wǎng)絡的普及,大家對 App 安裝包體積不再那么敏感了,只要我們遵循一些最佳實踐,一般不會在這一塊有太大的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-687218.html
到了這里,關于ReactNative進階(三十四):Jenkins 流水線 組包 iOS 應用包 ipa Archive 階段報錯error: Multiple commands produce問題修復及思考的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!