app在3.0版本时安装包已达到66.1M,app瘦身刻不容缓。 App安装包是由资源和可执行文件两部分组成,安装包瘦身也是从这两部分进行。
1.工程资源文件瘦身
工程资源文件主要是图片,资源大小与图片质量和数量息息相关。
分析图片的数量,质量
使用脚本获取工程中的所有图片资源,大小。工程共有722张图片,占用了8.2M的空间。图片大小分类如图:
其中10k以下占用率绝大部分,但还是有很多图片超过了100k,甚至有一些图片达到了1M.太恐怖了。。。建议把大文件替换。
图片文件压缩
无损压缩工具ImageOptiom(推荐)。这是一款非常好的图片压缩工具,可以进行无损压缩,能够对 png 和 jpeg 图片文件进行优化,它能找到最佳的压缩参数(在设置中可以设置压缩比例,80% 及以上是无损压缩,推荐使用),并通过消除不必要的信息(如文件的 EXIF 标签和颜色配置文件等),优化后达到减小文件大小的效果。
使用了一张950k的图片经过ImageOptiom压缩后586k,节省了38.3%的空间。
清除无用的资源文件
推荐使用工具LSUnusedResources。
接下来,打开工具LSUnusedResources,点击“Browse...”按钮,选择工程所在目录,点击"Search"按钮,即可开始搜索,如下图所示:
工程中有234张图片未引用,但这个不准因为它只是针对源码、Xib、Storyboard 和 plist 等文件,先全文搜索其中可能是引用了资源的字符串,然后用资源名和字符串做匹配,而拼接的图片会当做未使用的图片。因此搜索出后还需开发确认是否是拼接图片。
可执行文件的瘦身
LinkMap文件是Xcode产生可执行文件的同时生成的链接信息,用来描述可执行文件的构造成分,包括代码段(__TEXT)和数据段(__DATA)的分布情况。
在Xcode中,选择XCode -> Target -> Build Settings -> 搜map -> 把Write Link Map File选项设为YES,并指定好linkMap的存储位置。
编译后,到编译目录里找到该txt文件,文件名和路径就是上述的Path to Link Map File。这个LinkMap里展示了整个可执行文件的全貌,列出了编译后的每一个.o目标文件的信息(包括静态链接库.a里的),以及每一个目标文件的代码段,数据段存储详情。
import os
import re
import shutil
import sys
path = '/Users/mini5/Desktop/SmartHomeV6-LinkMap--.txt'
analyzeAllMoundle = False #统计每个模块.o的统计大小
class SymbolModel:
file = ""
size = 0
def verify_linkmapfile(path):
if not os.path.isfile(path):
print("请输入文件")
return False
file = open(path)
content = file.read()
file.close()
#查找是否存在# Object files:
if content.find("# Object files:") == -1:
print("输入linkmap文件非法")
return False
#查找是否存在# Sections:
if content.find("# Sections:") == -1:
print("输入linkmap文件非法")
return False
#查找是否存在# Symbols:
if content.find("# Symbols:") == -1:
print("输入linkmap文件非法")
return False
return True
def symbolMapFromContent():
symbolMap = {}
reachFiles = False
reachSections = False
reachSymblos = False
file = open(path)
for line in file.readlines():
if line.startswith("#"):
if line.startswith("# Object files:"):
reachFiles = True
if line.startswith("# Sections:"):
reachSections = True
if line.startswith("# Symbols:"):
reachSymblos = True
else:
if reachFiles == True and reachSections == False and reachSymblos == False:
#查找 files 列表,找到所有.o文件
location = line.find("]")
if location != -1:
key = line[:location+1]
if symbolMap.get(key) is not None:
continue
symbol = SymbolModel()
symbol.file = line[location + 1:]
symbolMap[key] = symbol
elif reachFiles == True and reachSections == True and reachSymblos == True:
#'\t'分割成三部分,分别对应的是Address,Size和 File Name
symbolsArray = line.split('\t')
if len(symbolsArray) == 3:
fileKeyAndName = symbolsArray[2]
#16进制转10进制
size = int(symbolsArray[1],16)
location = fileKeyAndName.find(']')
if location != -1:
key = fileKeyAndName[:location + 1]
symbol = symbolMap.get(key)
if symbol is not None:
symbol.size = symbol.size + size
file.close()
return symbolMap
def sortSymbol(symbolList):
return sorted(symbolList, key=lambda s: s.size,reverse = True)
def buildResultWithSymbols(symbols):
results = ["文件大小\t文件名称\r\n"]
totalSize = 0
for symbol in symbols:
results.append(calSymbol(symbol))
totalSize += symbol.size
results.append("总大小: %.2fM" % (totalSize/1024.0/1024.0))
return results
def buildCombinationResultWithSymbols(symbols):
#统计不同模块大小
results = ["库大小\t库名称\r\n"]
totalSize = 0
combinationMap = {}
for symbol in symbols:
names = symbol.file.split('/')
name = names[len(names) - 1].strip('\n')
location = name.find("(")
if name.endswith(")") and location != -1:
component = name[:location]
combinationSymbol = combinationMap.get(component)
if combinationSymbol is None:
combinationSymbol = SymbolModel()
combinationMap[component] = combinationSymbol
combinationSymbol.file = component
combinationSymbol.size = combinationSymbol.size + symbol.size
else:
#symbol可能来自app本身的目标文件或者系统的动态库
combinationMap[symbol.file] = symbol
sortedSymbols = sortSymbol(combinationMap.values())
for symbol in sortedSymbols:
results.append(calSymbol(symbol))
totalSize += symbol.size
results.append("总大小: %.2fM" % (totalSize/1024.0/1024.0))
return results
def calSymbol(symbol):
size = ""
if symbol.size / 1024.0 / 1024.0 > 1:
size = "%.2fM" % (symbol.size / 1024.0 / 1024.0)
else:
size = "%.2fK" % (symbol.size / 1024.0)
names = symbol.file.split('/')
if len(names) > 0:
size = "%s\t%s" % (size,names[len(names) - 1])
return size
def analyzeLinkMap():
if verify_linkmapfile(path) == True:
print("**********正在开始解析*********")
symbolDic = symbolMapFromContent()
symbolList = sortSymbol(symbolDic.values())
if analyzeAllMoundle:
results = buildCombinationResultWithSymbols(symbolList)
else:
results = buildResultWithSymbols(symbolList)
for result in results:
print(result)
print("***********解析结束***********")
if __name__ == "__main__":
analyzeLinkMap()
这里可以统计每个文件或每个库的大小,该舍弃的舍弃。
其他方式
清理无用类,无用方法,编译选项优化,好吧,我懒不想写了。。。。。。。。。。。