U8SDK——修正dex文件函数个数计算方式
之前写的那篇处理dex文件中函数个数超出65K之后的自动分割dex的方法有一个问题(之前的文章),就是从smali文件中计算函数个数,和最终dex文件中计算的函数个数不一致,严格来说,是小了很多。
后来,研究了下,发现dex文件中所谓的函数个数65K上限,指的是除了dex中本身定义的函数之外,还包含引用的外部方法(这些方法主要就是android本身的一些函数)
所以,我就在查, dex中函数个数计算公式到底是啥。不过,官方文档给出的就是一个简单的说法,就是引用的函数个数(不重复的)
后来,没办法,只能自己做实验,写了一个简单的Android应用,打包成apk,看dex中的函数个数,然后再将dex通过打包工具转换为smali,再研究smali中怎么样计算才能和dex文件中函数个数匹配上。
经过一系列的试验,主要有如下几点:
1、所有定义的函数算一次,如果这个函数同时也被调用了,不再计算次数
2、重载的函数,算多次
3、多个子类中调用同一个父类的方法,如果直接调用,不加super.,算多次
计算要点确认之后,我们就可以着手在smali文件中按照这个规则来计算函数个事了。
smali文件的格式,方法定义是”.method”开头;方法调用是“invoke-”开头;方法参数列表是多个参数的拼接,无分割符,不过对于我们来说, 方法的本身就是(方法名称+参数列表),所以,我们不用分割方法和参数,而是将其整体作为一个方法,这样重载的方法就可以区分了。(有兴趣的同学可以去了解下smali文件格式)
针对这几点要素,我们重新调整了之前函数个数计算的方式,我们增加了一个smali_utils.py来处理smali相关的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
import os import os.path import re import platform import subprocess import inspect import sys import codecs import threading import time import log_utils def get_smali_method_count(smaliFile, allMethods): if not os.path.exists(smaliFile): return 0 f = open(smaliFile) lines = f.readlines() f.close() classLine = lines[0] classLine.strip() if not classLine.startswith(".class"): log_utils.error(f + " not startswith .class") return 0 className = parse_class(classLine) #log_utils.debug("the class Name is "+className) count = 0 for line in lines: line = line.strip() method = None if line.startswith(".method"): method = parse_method_default(className, line) elif line.startswith("invoke-"): method = parse_method_invoke(line) if method is None: continue #log_utils.debug("the method is "+method) if method not in allMethods: count = count + 1 allMethods.append(method) else: pass #log_utils.debug(method + " is already exists in allMethods.") return count def parse_class(line): if not line.startswith(".class"): log_utils.error("line parse error. not startswith .class : "+line) return None blocks = line.split() return blocks[len(blocks)-1] def parse_method_default(className, line): if not line.startswith(".method"): log_utils.error("the line parse error in parse_method_default:"+line) return None blocks = line.split() return className + "->" + blocks[len(blocks)-1] def parse_method_invoke(line): if not line.startswith("invoke-"): log_utils.error("the line parse error in parse_method_invoke:"+line) blocks = line.split() return blocks[len(blocks)-1] |
然后,我们在apk_utils.py中,将我们之前的splitDex这个方法也调整下,改为调用这个文件中的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
def splitDex(workDir, decompileDir): """ 如果函数上限超过限制,自动拆分smali,以便生成多个dex文件 """ log_utils.info("now to check split dex ... ") smaliPath = decompileDir + "/smali" multidexFilePath = file_utils.getFullPath(smaliPath + "/android/support/multidex/MultiDex.smali") if not os.path.exists(multidexFilePath): #android-support-multidex.jar不存在,从local下面拷贝,并编译 dexJar = file_utils.getFullPath('config/local/android-support-multidex.jar') if not os.path.exists(dexJar): log_utils.error("the method num expired of dex, but no android-support-multidex.jar in u8.apk or in local folder") return targetPath = file_utils.getFullPath(workDir + "/local") if not os.path.exists(targetPath): os.makedirs(targetPath) file_utils.copy_file(dexJar, targetPath+"/android-support-multidex.jar") jar2dex(targetPath, targetPath) smaliPath = file_utils.getFullPath(decompileDir + "/smali") ret = dex2smali(targetPath + '/classes.dex', smaliPath) allFiles = [] allFiles = file_utils.list_files(decompileDir, allFiles, []) maxFuncNum = 65535 currFucNum = 0 totalFucNum = 0 currDexIndex = 1 allRefs = [] #保证U8Application等类在第一个classex.dex文件中 for f in allFiles: f = f.replace("\\", "/") if "/com/u8/sdk" in f or "/android/support/multidex" in f: currFucNum = currFucNum + smali_utils.get_smali_method_count(f, allRefs) totalFucNum = currFucNum for f in allFiles: f = f.replace("\\", "/") if not f.endswith(".smali"): continue if "/com/u8/sdk" in f or "/android/support/multidex" in f: continue thisFucNum = smali_utils.get_smali_method_count(f, allRefs) totalFucNum = totalFucNum + thisFucNum if currFucNum + thisFucNum >= maxFuncNum: currFucNum = thisFucNum currDexIndex = currDexIndex + 1 newDexPath = os.path.join(decompileDir, "smali_classes"+str(currDexIndex)) os.makedirs(newDexPath) else: currFucNum = currFucNum + thisFucNum if currDexIndex > 1: targetPath = f[0:len(decompileDir)] + "/smali_classes"+str(currDexIndex) + f[len(smaliPath):] file_utils.copy_file(f, targetPath) file_utils.del_file_folder(f) log_utils.info("the total func num:"+str(totalFucNum)) log_utils.info("split dex success. the classes.dex num:"+str(currDexIndex)) |
这样重新调整之后,测试了几个apk,和几个渠道,最终计算出来的函数个数, 和通过最终的dex比对,发现函数个数都是一致的。dex自动拆分功能也正常。
欢迎同学们进行测试, 如果有发现不一致的,请及时告诉我哦~~~
本文出自 U8SDK技术博客,转载时请注明出处及相应链接。
本文永久链接: http://www.uustory.com/?p=2069