問題:
App Store規(guī)定安裝包大小超過150MB的App只能在WIFI環(huán)境下載。現(xiàn)在項目App包已經超過這條線,這意味著可能將損失大量用戶,需要對其進行瘦身
App現(xiàn)狀
archive后,ipa包大小為132.3MB,.app大小為166MB,Mach-O大小為86.1MB,Assets.car大小為61.4MB
ipa包:
archive后生成的文件,實際為一種壓縮包,解壓后包含.app文件和symbols信息。根據(jù)app store規(guī)定,.app文件超過150MB則無法進行OTA升級
app文件:
實際的應用程序包,里面包含app同名的Mach-O可執(zhí)行文件,Asset.car、.nib、.bundle、Localizable.strings等資源文件,_CodeSignature文件夾包含簽名信息。
Mach-O文件:
代碼編譯鏈接后生成的可執(zhí)行文件,包含支持的所有cpu指令集。可以通過Link-Map分析其中所有方法和類所占空間的大小。
Assets.car:
xcassets圖片文件。
因此,對App瘦身實際上就是減小.app文件的大小
查看.app包內容規(guī)劃此次App瘦身的目標:針對大于100Kb的文件進行瘦身,包含以下兩點:
1.整理資源文件,包括文件壓縮與無用資源的刪除
2.整理可執(zhí)行文件,包括刪除無用類和無用函數(shù)
瘦身方法
1.LSUnusedResources查找并刪除無用文件
先勾選"Ignore similar name"過濾掉以"tag_%d"命名的文件后再去掉勾選,多篩幾遍
瘦身效果:129.1MB;app文件大小:160.8MB; X1可執(zhí)行文件:86.1MB;Assets.car:56.2MB
2.Link-Map分析并刪除無用三方庫
使用Link-Map分析工具得出每個類或者庫所占用的空間大小,可以快速定位需要優(yōu)化的類或靜態(tài)庫。
瘦身效果:123.7MB;app文件大小:156.8MB; X1可執(zhí)行文件:84.1MB;Assets.car:56.2MB
3.用LaunchScreen.storyboard替代啟動圖
上圖.app包中PNG圖片占用大量空間,全部為LaunchImage中的全尺寸啟動圖。刪除后用storyboard替代,注意為UI控件添加合適約束以適應不同尺寸的手機。
瘦身效果:ipa包大小:116.1MB;app文件大小:147.7MB; X1可執(zhí)行文件:84.1MB;Assets.car:56.2MB
4.選擇合適的cpu指令集進行編譯
使用MachOView查看app中的Mach-O文件,armv7占了一小半
9012年如果追求app體積最優(yōu)解,可以去掉對armv7的支持(iphone4,iphone4s),當然指令集是向下兼容的,如果想全尺寸支持可以選擇armv7(iphone5,iphone5c向下兼容armv7)和arm64。這里我選擇去掉armv7。
瘦身效果:ipa包大小:80.4MB;app文件大小:107.5MB; X1可執(zhí)行文件:45.9MB;Assets.car:56.2MB
5.python腳本查找并刪除無用類
分析了兩天破解版和未破解版的AppCode,得出的unused code只有import問題,后來參考這里,一個簡單的腳本分析出無用的類。
# -*- coding: UTF-8 -*-
#!/usr/bin/env python
# 使用方法:python py文件 Xcode工程文件目錄
import sys
import os
import re
if len(sys.argv) == 1:
print '請在.py文件后面輸入工程路徑'
sys.exit()
projectPath = sys.argv[1]
print '工程路徑為%s' % projectPath
resourcefile = []
totalClass = set([])
unusedFile = []
pbxprojFile = []
def Getallfile(rootDir):
for lists in os.listdir(rootDir):
path = os.path.join(rootDir, lists)
if os.path.isdir(path):
Getallfile(path)
else:
ex = os.path.splitext(path)[1]
if ex == '.m' or ex == '.mm' or ex == '.h':
resourcefile.append(path)
elif ex == '.pbxproj':
pbxprojFile.append(path)
Getallfile(projectPath)
print '工程中所使用的類列表為:'
for ff in resourcefile:
print ff
for e in pbxprojFile:
f = open(e, 'r')
content = f.read()
array = re.findall(r'\s+([\w,\+]+\.[h,m]{1,2})\s+',content)
see = set(array)
totalClass = totalClass|see
f.close()
print '工程中所引用的.h與.m及.mm文件'
for x in totalClass:
print x
print '--------------------------'
for x in resourcefile:
ex = os.path.splitext(x)[1]
if ex == '.h': #.h頭文件可以不用檢查
continue
fileName = os.path.split(x)[1]
print fileName
if fileName not in totalClass:
unusedFile.append(x)
for x in unusedFile:
resourcefile.remove(x)
print '未引用到工程的文件列表為:'
writeFile = []
for unImport in unusedFile:
ss = '未引用到工程的文件:%s\n' % unImport
writeFile.append(ss)
print unImport
unusedFile = []
allClassDic = {}
for x in resourcefile:
f = open(x,'r')
content = f.read()
array = re.findall(r'@interface\s+([\w,\+]+)\s+:',content)
for xx in array:
allClassDic[xx] = x
f.close()
print '所有類及其路徑:'
for x in allClassDic.keys():
print x,':',allClassDic[x]
def checkClass(path,className):
f = open(path,'r')
content = f.read()
if os.path.splitext(path)[1] == '.h':
match = re.search(r':\s+(%s)\s+' % className,content)
else:
match = re.search(r'(%s)\s+\w+' % className,content)
f.close()
if match:
return True
ivanyuan = 0
totalIvanyuan = len(allClassDic.keys())
for key in allClassDic.keys():
path = allClassDic[key]
index = resourcefile.index(path)
count = len(resourcefile)
used = False
offset = 1
ivanyuan += 1
print '完成',ivanyuan,'共:',totalIvanyuan,'path:%s'%path
while index+offset < count or index-offset > 0:
if index+offset < count:
subPath = resourcefile[index+offset]
if checkClass(subPath,key):
used = True
break
if index - offset > 0:
subPath = resourcefile[index-offset]
if checkClass(subPath,key):
used = True
break
offset += 1
if not used:
str = '未使用的類:%s 文件路徑:%s\n' %(key,path)
unusedFile.append(str)
writeFile.append(str)
for p in unusedFile:
print '未使用的類:%s' % p
filePath = os.path.split(projectPath)[0]
writePath = '%s/未使用的類.txt' % filePath
f = open(writePath,'w+')
f.writelines(writeFile)
f.close()
刪除項目中無用類。
瘦身效果:ipa包大小:80.1MB;app文件大小:107.2MB; X1可執(zhí)行文件:45.6MB;Assets.car:56.2MB
6.xcassets圖片壓縮
項目使用xcassets管理圖片
1)使用ImageOptime工具進行后,編譯時xcode會還原壓縮后的png圖片,需要設置COMPRESS_PNG_FILES
和STRIP_PNG_TEXT
為No
2)TinyPng對大尺寸PNG圖片進行壓縮,免費版每日有壓縮數(shù)量限制。項目壓縮130Kb以上png圖片,再替換xcassets中原圖。
瘦身效果:ipa包大小:69.5MB;app文件大小:96.4MB; X1可執(zhí)行文件:45.6MB;Assets.car:45.3MB
小結
總結就是資源和代碼兩方面下手,xcode編譯設置其實作用感覺不太大,其實最關鍵的還是需要結合自身項目,分析其構成,來找到適合自己項目的瘦身方法。