iOS相关技术实现从六开始——文件分析与瘦身

parselinkmap.py源码分析

2019-10-22  本文已影响0人  肠粉白粥_Hoben

LinkMap初探提到了自动分析LinkMap的开源项目,今天来分析一下。

其实源码里面的核心思想挺简单的,就是用一个字典{symbol:size},计算总体积的时候,枚举每一个key,相加即可。

一. 读取文件

1. 确保字段完整

def read_base_link_map_file(base_link_map_file, base_link_map_result_file):
    try:
        link_map_file = open(base_link_map_file)
    except IOError:
        print "Read file " + base_link_map_file + " failed!"
        return
    else:
        try:
            content = link_map_file.read()
        except IOError:
            print "Read file " + base_link_map_file + " failed!"
            return
        else:
            obj_file_tag_index = content.find("# Object files:")
            # +15的意思是读取下一行
            sub_obj_file_symbol_str = content[obj_file_tag_index + 15:]
            symbols_index = sub_obj_file_symbol_str.find("# Symbols:")
            if obj_file_tag_index == -1 or symbols_index == -1 or content.find("# Path:") == -1:
                print "The Content of File " + base_link_map_file + " is Invalid."
                pass

上述代码是为了确保该文件中存在File段和Symbol段,这样才能开始进行下面的读取文件的操作。

2. 读取Files字段

Files字段长这样:

# Object files:
[  0] linker synthesized
[  1] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/TestSuperDemo.app-Simulated.xcent
[  2] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/ViewController.o
[  3] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/SonViewController.o
[  4] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/main.o
[  5] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/AppDelegate.o
[  6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
[  7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libobjc.tbd
[  8] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libSystem.tbd
[  9] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd

我们可以通过这一段来找到序号和文件名的对应,后面分析size的时候就可以以文件名为key,size为value去分析了。

link_map_file_tmp = open(base_link_map_file)
reach_files = 0
reach_sections = 0
reach_symbols = 0
size_map = {}
while 1:
    line = link_map_file_tmp.readline()
    if not line:
        break
    if line.startswith("#"):
        if line.startswith("# Object files:"):
            reach_files = 1
            pass
        if line.startswith("# Sections"):
            reach_sections = 1
            pass
        if line.startswith("# Symbols"):
            reach_symbols = 1
            pass
        pass
    else:
        if reach_files == 1 and reach_sections == 0 and reach_symbols == 0:
            index = line.find("]")
            if index != -1:
                symbol = {"file": line[index + 2:-1]}
                key = int(line[1: index])
                size_map[key] = symbol
            pass

上面的代码就是把序号取出来,作为key,把路径取出来,作为value一一对应。

3. Symbols字段

Symbols字段长这样:

# Symbols:
# Address   Size        File  Name
0x100001660 0x00000460  [  2] -[ViewController init]
0x100001AC0 0x00000040  [  2] -[ViewController viewDidLoad]
0x100001B00 0x000000B0  [  2] -[ViewController push]
x100001BB0  0x00000030  [  2] -[ViewController shouldNotPrint]
0x100001BE0 0x00000020  [  2] -[ViewController button]
0x100001C00 0x00000040  [  2] -[ViewController setButton:]
0x100001C40 0x00000020  [  2] -[ViewController allPlate]
0x100001C60 0x00000040  [  2] -[ViewController setAllPlate:]
0x100001CA0 0x00000020  [  2] -[ViewController currentPlateInfo]
0x100001CC0 0x00000040  [  2] -[ViewController setCurrentPlateInf

可以看到,Symbols字段有十六进制的Size,每一个序号对应一个文件,只要我们把这个序号的Size相加,即可得到这个文件的大小了。

elif reach_files == 1 and reach_sections == 1 and reach_symbols == 1:
    symbols_array = line.split("\t")
    if len(symbols_array) == 3:
        file_key_and_name = symbols_array[2]
        size = int(symbols_array[1], 16)
        index = file_key_and_name.find("]")
        if index != -1:
            key = file_key_and_name[1:index]
            key = int(key)
            # 根据symbols字段里面的key,回到files字段找到对应的.o文件
            symbol = size_map[key]
            if symbol:
                if "size" in symbol:
                    symbol["size"] += size
                    pass
                else:
                    symbol["size"] = size
                pass
            pass
        pass
    pass

二. 体积计算

1. 计算文件体积

上面读取文件完毕后,已经将所有.o文件的大小都获取到,并存在了size_map里面,size_map里的内容如下所示:

size_map = {
    "100" : {                                   # 序号
        "symbol" : {
            "size" : 1024B,                     # 大小
            "file" : "User/Document/File.o"     # 路径
        }
    }
}

其中,路径中的文件不仅仅是.o文件,也有可能是.a文件(包含若干个.o文件),要分析.a文件的大小,则需要把.a文件里面所有的.o文件体积加起来。

.a文件的名字像这样:libAFNetworking.a(AFNetworking-dummy.o),所以要获取.a文件名字,就需要用(分开。

这里我额外加了白名单机制,因为有些文件加入了#ifDebug就会没有size字段,过滤掉免得显示一些警告信息。

for key in size_map:
    symbol = size_map[key]
    if "size" in symbol:
        total_size += symbol["size"]
        # .o文件和.a文件,如libAFNetworking.a(AFNetworking-dummy.o)就可以区分开
        # 普通的.o文件就会按.o计算
        # .a文件会统一计算
        o_file_name = symbol["file"].split("/")[-1]
        a_file_name = o_file_name.split("(")[0]
        if a_file_name in a_file_map:
            a_file_map[a_file_name] += symbol["size"]
            pass
        else:
            a_file_map[a_file_name] = symbol["size"]
            pass
        pass

    else:
        o_file_name = symbol["file"].split("/")[-1]
        if o_file_name in debug_white_list:
            pass
        else:
            print "WARN : some error occurred for [file, key] : ",
            print "[" + o_file_name + ", " + key.__str__() + "]"

2. 体积大小排序

# 根据size从大到小排序
a_file_sorted_list = sorted(a_file_map.items(), key=lambda x: x[1], reverse=True)

这个排序的意思是,从大到小,根据a_file_map的value(即size)排序。

3. 打印文件

print "%s" % "=".ljust(80, '=')
print "%s" % (base_link_map_file+"各模块体积汇总").center(87)
print "%s" % "=".ljust(80, '=')
if os.path.exists(base_link_map_result_file):
    os.remove(base_link_map_result_file)
    pass
print "Creating Result File : %s" % base_link_map_result_file
output_file = open(base_link_map_result_file, "w")
for item in a_file_sorted_list:
    print "%s%.2fM" % (item[0].ljust(50), item[1]/1024.0/1024.0)
    output_file.write("%s \t\t\t%.2fM\n" % (item[0].ljust(50), item[1]/1024.0/1024.0))
    pass
print "%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0)
print "\n\n\n\n\n"
output_file.write("%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0))
link_map_file_tmp.close()
output_file.close()

现在获得的size单位是B,要转化为MB,则需要除以1024的平方,最后输出打印即可(他还顺便生成了一个结果文件)。

三. 调用

1. 分析单个文件

直接调用了read_base_link_map_file函数,输入为LinkMap文件路径、输出结果路径。

2. 对比分析两个文件

对比的文件也调用了read_base_link_map_file方法,输出两个output_file,格式类似于:

================================================================================
                     demoData/BaseLinkMap.txt各模块体积汇总
================================================================================
Creating Result File : demoData/BaseLinkMapResult.txt
AppDelegate.o                                     0.01M
ViewController.o                                  0.00M
TestCleanPackage.app.xcent                        0.00M
UnUsedClass.o                                     0.00M
main.o                                            0.00M
libobjc.tbd                                       0.00M
linker synthesized                                0.00M
Foundation.tbd                                    0.00M
UIKit.tbd                                         0.00M
总体积:                                           0.01M

然后对各个文件体积和总体积进行分词(其他字段忽略),并存入了字典:

def parse_result_file(result_file_name):
    base_bundle_list = []
    result_file = open(result_file_name)
    while 1:
        line = result_file.readline()
        if not line:
            break
        bundle_and_size = line.split()
        if len(bundle_and_size) == 2 and line.find(":") == -1:
            bundle_and_size_map = {"name": bundle_and_size[0], "size": bundle_and_size[1]}
            base_bundle_list += [bundle_and_size_map]
            pass
    return base_bundle_list

最后compare方法将两个文件的字典的size取出来,进行对比,如果后者文件更大点则输出,如果后者有而前者没有也会输出。

上一篇下一篇

猜你喜欢

热点阅读