U8SDK——支持自动拆分成多个dex文件(MultiDex支持)
Android应用程序,最终发布成一个apk,安装到手机上。 apk文件随便用一个解压缩文件打开,可以看到里面有一个classes.dex文件,这就是之前工程中所有的代码,以及所有依赖的jar包全部合并在一起生成的一个dex文件。关于dex文件是什么,可以自己去科普一下。
google当初在设计dex文件的时候,限制了dex文件中最大的函数个数为65536(unsigned short),如果超出这个限制,那么如果不采用特殊处理,打包的时候,就会抛出:
org.jf.util.ExceptionWithContext: Unsigned short value out of range: ***
类似这样的错误。
对于游戏开发来说,我们目前主要的问题是, 一个游戏本身采用的可能是unity或者cocos2dx等游戏引擎开发, 纯粹的Android代码并不多, 但是由于目前手机游戏需要接入很多渠道SDK, 部分渠道SDK的jar包是相当大的,里面对应的函数数量自然也少不了。这就会导致,这些渠道SDK在集成到游戏中,我们再次打包,会出现函数数量越界的问题。
另一个问题,就是我们除了接入渠道SDK之外,往往还需要同时接入统计,分享,推送等其他功能性SDK,所有这些jar包加起来,函数数量越界的可能就更大了。
最近不少用U8SDK的同学,遇到了这个问题。游戏母包,在通过U8SDK打包工具进行打包部分渠道(比如百度,360)的时候,在回编译的时候,抛出上面的错误。 这是因为百度和360渠道本身的函数数量已经接近这个上线的值了,再加上母包本身的函数,导致最终dex中的函数数量越界。
这里,就来统一处理下这个问题,这里我们先列出整体的思路
1、打包工具,在回编译的之前, 先找出总共的函数数量,如果超出65536的限制,我们自动拆分出多个dex
2、apktool反编译之后,我们统一的代码格式为smali,我们需要将多余的smali文件,移到smali_classes2,smali_classes3…等等,目前最多支持5个,应该够用了。
3、U8Application中,在attachBaseContext 中,我们调用一下MultiDex.install(this); 以便,对多dex文件的支持。
4、多个dex文件的支持,我们采用google提供的android-support-multidex.jar。我们将这个jar包,放在打包工具/config/local目录下,如果母包或者渠道SDK中不存在这个jar包,我们从这里将这个jar包添加进来
5、因为程序入口是U8Application,所以我们必须保证U8Application等类和multidex这个jar包所有的文件在第一个classes.dex文件中,否则程序初始化就会找不到这个类。
根据这个思路,我们在打包工具/scripts/apk_utils.py中,增加一个函数:
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 |
def splitDex(workDir, decompileDir): """ 如果函数上限超过限制,自动拆分smali,以便生成多个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 currDexIndex = 1 #保证U8Application等类在第一个classex.dex文件中 for f in allFiles: f = f.replace("\\", "/") if "/com/u8/sdk" in f or "/android/support/multidex" in f: currFucNum = currFucNum + file_utils.get_smali_func_num(f) 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 currFucNum = currFucNum + file_utils.get_smali_func_num(f) if currFucNum >= maxFuncNum: currFucNum = 0 currDexIndex = currDexIndex + 1 newDexPath = os.path.join(decompileDir, "smali_classes"+str(currDexIndex)) os.makedirs(newDexPath) 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) print("split dex success. the classes.dex num:"+str(currDexIndex)) |
这个函数的功能,就是找出所有的函数数量,大于最大阀值的部分,我们依次拷贝到额外的smali文件夹中。
这里用到file_utils.py中增加的一个获取smali文件中函数数量的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def get_smali_func_num(smaliFile): if not os.path.exists(smaliFile): return 0 f = open(smaliFile) lines = f.readlines() f.close() num = 0 for line in lines: if line.startswith(".method"): num = num + 1 return num |
有了这个,我们在core.py中,在回编译(apk_utils.recompileApk)之前,调用apk_utils.splitDex(workDir, decompileDir)。
这样,打包的时候,就可以自动拆分出多个dex文件了。 记得在打包工具/config/local/中放一个android-support-multidex.jar。 这个jar包可以从Android SDK安装目录/extras/android/support/multidex/library/libs中拷贝
这样,你再打包,如果函数个数超出上线,那么会生成classes.dex,classes2.dex…..
最后,我们在抽象层U8SDK2中的U8Application类中的attachBaseContext 中调用一下(先将android-support-multidex.jar拷贝到U8SDK2抽象层的libs中,必要时,引用下这个jar包,否则可能找不到MultiDex)
1 2 |
MultiDex.install(this); |
然后重新编译,在bin目录下,会生成最新的u8sdk2.jar包,拷贝到游戏工程中。然后,重新打母包,然后打出渠道包。
新的方式,测试下来是OK的,唯一需要注意的一点,是入口Application等类必须在第一个classex.dex文件中。
备注:
后来发现上面计算函数个数的方式有问题, 和实际dex中函数个数计算方式不同,没有考虑调用的情况,导致计算的函数个数比实际的要小,不过目前已经修正了这个问题, 请看这篇帖子
本文出自 U8SDK技术博客,转载时请注明出处及相应链接。
本文永久链接: http://www.uustory.com/?p=2061