ResProguardCheckTask
目的:判断apk是否执行了资源混淆。
@Override
public TaskResult call() throws TaskExecuteException {
File resDir = new File(inputFile, ApkConstants.RESOURCE_DIR_PROGUARD_NAME);
...
if (resDir.exists() && resDir.isDirectory()) {
Log.d(TAG, "find resource directory " + resDir.getAbsolutePath());
//有名为r的文件夹,执行了支援混淆
((TaskJsonResult) taskResult).add("hasResProguard", true);
} else {
resDir = new File(inputFile, ApkConstants.RESOURCE_DIR_NAME);
if (resDir.exists() && resDir.isDirectory()) {
File[] dirs = resDir.listFiles();
boolean hasProguard = true;
for (File dir : dirs) {
//任意文件夹不符合资源混淆的命名规则,则未执行资源混淆
if (dir.isDirectory() && !fileNamePattern.matcher(dir.getName()).matches()) {
hasProguard = false;
Log.i(TAG, "directory " + dir.getName() + " has a non-proguard name!");
break;
}
}
((TaskJsonResult) taskResult).add("hasResProguard", hasProguard);
...
}
FindNonAlphaPngTask
目的:检测出没有透明度的png文件(应该使用jpg替换,占用空间会更小)
private void findNonAlphaPng(File file) throws IOException {
if (file != null) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File tempFile : files) {
findNonAlphaPng(tempFile);
}
} else if (file.isFile() && file.getName().endsWith(ApkConstants.PNG_FILE_SUFFIX) && !file.getName().endsWith(ApkConstants.NINE_PNG)) {
BufferedImage bufferedImage = ImageIO.read(file);
//没有alpha信息
if (!bufferedImage.getColorModel().hasAlpha()) {
String filename = file.getAbsolutePath().substring(inputFile.getAbsolutePath().length() + 1);
if (entryNameMap.containsKey(filename)) {
filename = entryNameMap.get(filename);
}
long size = file.length();
if (entrySizeMap.containsKey(filename)) {
size = entrySizeMap.get(filename).getFirst();
}
if (size >= downLimitSize * ApkConstants.K1024) {
nonAlphaPngList.add(Pair.of(filename, file.length()));
}
}
}
}
}
MultiLibCheckTask
目的:检测lib文件夹中是否有多文件夹存在。
@Override
public TaskResult call() throws TaskExecuteException {
try {
TaskResult taskResult = TaskResultFactory.factory(getType(), TASK_RESULT_TYPE_JSON, config);
if (taskResult == null) {
return null;
}
long startTime = System.currentTimeMillis();
JsonArray jsonArray = new JsonArray();
if (libDir.exists() && libDir.isDirectory()) {
File[] dirs = libDir.listFiles();
for (File dir : dirs) {
if (dir.isDirectory()) {
jsonArray.add(dir.getName());
}
}
}
((TaskJsonResult) taskResult).add("lib-dirs", jsonArray);
if (jsonArray.size() > 1) {
((TaskJsonResult) taskResult).add("multi-lib", true);
} else {
((TaskJsonResult) taskResult).add("multi-lib", false);
}
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
UncompressedFileTask
目的:比对apk压缩包里每一个entry的压缩后大小、压缩前大小;若大小一样,则表示文件未压缩。
@Override
public TaskResult call() throws TaskExecuteException {
try {
...
if (!entrySizeMap.isEmpty()) { //take advantage of the result of UnzipTask.
for (Map.Entry<String, Pair<Long, Long>> entry : entrySizeMap.entrySet()) {
final String suffix = getSuffix(entry.getKey());
Pair<Long, Long> size = entry.getValue();
if (filterSuffix.isEmpty() || filterSuffix.contains(suffix)) {
if (!uncompressSizeMap.containsKey(suffix)) {
uncompressSizeMap.put(suffix, size.getFirst());
} else {
uncompressSizeMap.put(suffix, uncompressSizeMap.get(suffix) + size.getFirst());
}
if (!compressSizeMap.containsKey(suffix)) {
compressSizeMap.put(suffix, size.getSecond());
} else {
compressSizeMap.put(suffix, compressSizeMap.get(suffix) + size.getSecond());
}
} else {
// Log.d(TAG, "file: %s, filter by suffix.", entry.getKey());
}
}
}
for (String suffix : uncompressSizeMap.keySet()) {
//大小比对
if (uncompressSizeMap.get(suffix).equals(compressSizeMap.get(suffix))) {
JsonObject fileItem = new JsonObject();
fileItem.addProperty("suffix", suffix);
fileItem.addProperty("total-size", uncompressSizeMap.get(suffix));
jsonArray.add(fileItem);
}
}
((TaskJsonResult) taskResult).add("files", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
CountRTask
目的:统计R文件数量。
@Override
public TaskResult call() throws TaskExecuteException {
try {
TaskResult taskResult = TaskResultFactory.factory(type, TaskResultFactory.TASK_RESULT_TYPE_JSON, config);
long startTime = System.currentTimeMillis();
Map<String, String> classProguardMap = config.getProguardClassMap();
for (RandomAccessFile dexFile : dexFileList) {
DexData dexData = new DexData(dexFile);
dexData.load();
ClassRef[] defClassRefs = dexData.getInternalReferences();
for (ClassRef classRef : defClassRefs) {
String className = ApkUtil.getNormalClassName(classRef.getName());
if (classProguardMap.containsKey(className)) {
className = classProguardMap.get(className);
}
//去掉内部类
String pureClassName = getOuterClassName(className);
//识别R文件
if (pureClassName.endsWith(".R") || "R".equals(pureClassName)) {
if (!classesMap.containsKey(pureClassName)) {
classesMap.put(pureClassName, classRef.getFieldArray().length);
} else {
classesMap.put(pureClassName, classesMap.get(pureClassName) + classRef.getFieldArray().length);
}
}
}
}
JsonArray jsonArray = new JsonArray();
long totalSize = 0;
Map<String, String> proguardClassMap = config.getProguardClassMap();
for (Map.Entry<String, Integer> entry : classesMap.entrySet()) {
JsonObject jsonObject = new JsonObject();
if (proguardClassMap.containsKey(entry.getKey())) {
jsonObject.addProperty("name", proguardClassMap.get(entry.getKey()));
} else {
jsonObject.addProperty("name", entry.getKey());
}
jsonObject.addProperty("field-count", entry.getValue());
totalSize += entry.getValue();
jsonArray.add(jsonObject);
}
((TaskJsonResult) taskResult).add("R-count", jsonArray.size());
((TaskJsonResult) taskResult).add("Field-counts", totalSize);
((TaskJsonResult) taskResult).add("R-classes", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
DuplicateFileTask
目的:通过计算md5,判断apk中是否存在完全一样的文件。
private void computeMD5(File file) throws NoSuchAlgorithmException, IOException {
if (file != null) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File resFile : files) {
computeMD5(resFile);
}
} else {
MessageDigest msgDigest = MessageDigest.getInstance("MD5");
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[512];
int readSize = 0;
long totalRead = 0;
while ((readSize = inputStream.read(buffer)) > 0) {
msgDigest.update(buffer, 0, readSize);
totalRead += readSize;
}
inputStream.close();
if (totalRead > 0) {
final String md5 = Util.byteArrayToHex(msgDigest.digest());
String filename = file.getAbsolutePath().substring(inputFile.getAbsolutePath().length() + 1);
if (entryNameMap.containsKey(filename)) {
filename = entryNameMap.get(filename);
}
if (!md5Map.containsKey(md5)) {
md5Map.put(md5, new ArrayList<String>());
if (entrySizeMap.containsKey(filename)) {
fileSizeList.add(Pair.of(md5, entrySizeMap.get(filename).getFirst()));
} else {
fileSizeList.add(Pair.of(md5, totalRead));
}
}
//md5相同的文件列表
md5Map.get(md5).add(filename);
}
}
}
}
@Override
public TaskResult call() throws TaskExecuteException {
...
...
for (Pair<String, Long> entry : fileSizeList) {
//md5相同的文件
if (md5Map.get(entry.getFirst()).size() > 1) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("md5", entry.getFirst());
jsonObject.addProperty("size", entry.getSecond());
JsonArray jsonFiles = new JsonArray();
for (String filename : md5Map.get(entry.getFirst())) {
jsonFiles.add(filename);
}
jsonObject.add("files", jsonFiles);
jsonArray.add(jsonObject);
}
}
((TaskJsonResult) taskResult).add("files", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
return taskResult;
}
MultiSTLCheckTask
目的:判断so是否带有多份stl标准库。
@Override
public TaskResult call() throws TaskExecuteException {
try {
...
for (File libFile : libFiles) {
if (isStlLinked(libFile)) {
Log.d(TAG, "lib: %s has stl link", libFile.getName());
jsonArray.add(libFile.getName());
}
}
((TaskJsonResult) taskResult).add("stl-lib", jsonArray);
if (jsonArray.size() > 1) {
((TaskJsonResult) taskResult).add("multi-stl", true);
} else {
((TaskJsonResult) taskResult).add("multi-stl", false);
}
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
private boolean isStlLinked(File libFile) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(toolnmPath, "-D", "-C", libFile.getAbsolutePath());
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
while (line != null) {
String[] columns = line.split(" ");
// Log.d(TAG, "%s", line);
if (columns.length >= 3 && columns[1].equals("T") && columns[2].startsWith("std::")) {
return true;
}
line = reader.readLine();
}
reader.close();
process.waitFor();
return false;
}
UnusedResourcesTask
目的:检测出在代码、资源文件中未被引用的资源。
@Override
public TaskResult call() throws TaskExecuteException {
try {
TaskResult taskResult = TaskResultFactory.factory(type, TaskResultFactory.TASK_RESULT_TYPE_JSON, config);
long startTime = System.currentTimeMillis();
readMappingTxtFile();
readResourceTxtFile();
//添加所有声明的资源
unusedResSet.addAll(resourceDefMap.values());
Log.d(TAG, "find resource declarations %d items.", unusedResSet.size());
//找到所有代码中使用的资源
decodeCode();
Log.d(TAG, "find resource references in classes: %d items.", resourceRefSet.size());
//找到所有资源中引用的资源
decodeResources();
Log.d(TAG, "find resource references %d items.", resourceRefSet.size());
//去掉被引用的资源
unusedResSet.removeAll(resourceRefSet);
Log.d(TAG, "find unused references %d items", unusedResSet.size());
JsonArray jsonArray = new JsonArray();
for (String name : unusedResSet) {
jsonArray.add(name);
}
((TaskJsonResult) taskResult).add("unused-resources", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
private void readMappingTxtFile() throws IOException {
// com.tencent.mm.R$string -> com.tencent.mm.R$l:
// int fade_in_property_anim -> aRW
if (mappingTxt != null) {
BufferedReader bufferedReader = new BufferedReader(new FileReader(mappingTxt));
String line = bufferedReader.readLine();
boolean readRField = false;
String beforeClass = "", afterClass = "";
try {
while (line != null) {
if (!line.startsWith(" ")) {
String[] pair = line.split("->");
if (pair.length == 2) {
beforeClass = pair[0].trim();
afterClass = pair[1].trim();
afterClass = afterClass.substring(0, afterClass.length() - 1);
if (!Util.isNullOrNil(beforeClass) && !Util.isNullOrNil(afterClass) && ApkUtil.isRClassName(ApkUtil.getPureClassName(beforeClass))) {
// Log.d(TAG, "before:%s,after:%s", beforeClass, afterClass);
readRField = true;
} else {
readRField = false;
}
} else {
readRField = false;
}
} else {
if (readRField) {
String[] entry = line.split("->");
if (entry.length == 2) {
String key = entry[0].trim();
String value = entry[1].trim();
if (!Util.isNullOrNil(key) && !Util.isNullOrNil(value)) {
String[] field = key.split(" ");
if (field.length == 2) {
// Log.d(TAG, "%s -> %s", afterClass.replace('$', '.') + "." + value, getPureClassName(beforeClass).replace('$', '.') + "." + field[1]);
//添加 R.java中混淆后的全路径field -> R.java混淆前的全路径field
rclassProguardMap.put(afterClass.replace('$', '.') + "." + value, ApkUtil.getPureClassName(beforeClass).replace('$', '.') + "." + field[1]);
}
}
}
}
}
line = bufferedReader.readLine();
}
} finally {
bufferedReader.close();
}
}
}
private void readResourceTxtFile() throws IOException {
//读取R.txt
BufferedReader bufferedReader = new BufferedReader(new FileReader(resourceTxt));
String line = bufferedReader.readLine();
try {
while (line != null) {
String[] columns = line.split(" ");
if (columns.length >= 4) {
final String resourceName = "R." + columns[1] + "." + columns[2];
if (!columns[0].endsWith("[]") && columns[3].startsWith("0x")) {
//int styleable ActionBar_title 27
if (columns[3].startsWith("0x01")) {
Log.d(TAG, "ignore system resource %s", resourceName);
} else {
final String resId = parseResourceId(columns[3]);
if (!Util.isNullOrNil(resId)) {
//资源id 资源名称 映射
resourceDefMap.put(resId, resourceName);
}
}
} else {
//int[] styleable ActionMode { 0x7f030034, 0x7f030036, 0x7f030056, 0x7f0300ad, 0x7f030168, 0x7f03019e }
Log.d(TAG, "ignore resource %s", resourceName);
if (columns[0].endsWith("[]") && columns.length > 5) {
Set<String> attrReferences = new HashSet<String>();
for (int i = 4; i < columns.length; i++) {
if (columns[i].endsWith(",")) {
attrReferences.add(columns[i].substring(0, columns[i].length() - 1));
} else {
attrReferences.add(columns[i]);
}
}
//style映射
styleableMap.put(resourceName, attrReferences);
}
}
}
line = bufferedReader.readLine();
}
} finally {
bufferedReader.close();
}
}
解析dex文件中的smali代码:
private void decodeCode() throws IOException {
for (String dexFileName : dexFileNameList) {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(new File(inputFile, dexFileName), Opcodes.forApi(15));
BaksmaliOptions options = new BaksmaliOptions();
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
for (ClassDef classDef : classDefs) {
String[] lines = ApkUtil.disassembleClass(classDef, options);
if (lines != null) {
readSmaliLines(lines);
}
}
}
}
private void readSmaliLines(String[] lines) {
if (lines == null) {
return;
}
for (String line : lines) {
line = line.trim();
if (!Util.isNullOrNil(line)) {
if (line.startsWith("const")) {
String[] columns = line.split(",");
if (columns.length == 2) {
final String resId = parseResourceId(columns[1].trim());
//从id获取资源名
if (!Util.isNullOrNil(resId) && resourceDefMap.containsKey(resId)) {
resourceRefSet.add(resourceDefMap.get(resId));
}
}
} else if (line.startsWith("sget")) {
String[] columns = line.split(" ");
if (columns.length == 3) {
//获取资源名称
final String resourceRef = parseResourceNameFromProguard(columns[2]);
if (!Util.isNullOrNil(resourceRef)) {
//Log.d(TAG, "find resource reference %s", resourceRef);
if (styleableMap.containsKey(resourceRef)) {
//reference of R.styleable.XXX
for (String attr : styleableMap.get(resourceRef)) {
resourceRefSet.add(resourceDefMap.get(attr));
}
} else {
resourceRefSet.add(resourceRef);
}
}
}
}
}
}
}
private String parseResourceNameFromProguard(String entry) {
if (!Util.isNullOrNil(entry)) {
// sget v6, Lcom/tencent/mm/R$string;->chatting_long_click_menu_revoke_msg:I
// sget v1, Lcom/tencent/mm/libmmui/R$id;->property_anim:I
// sput-object v0, Lcom/tencent/mm/plugin_welab_api/R$styleable;->ActionBar:[I
// const v6, 0x7f0c0061
String[] columns = entry.split("->");
if (columns.length == 2) {
int index = columns[1].indexOf(':');
if (index >= 0) {
final String className = ApkUtil.getNormalClassName(columns[0]);
final String fieldName = columns[1].substring(0, index);
if (!rclassProguardMap.isEmpty()) {
String resource = className.replace('$', '.') + "." + fieldName;
if (rclassProguardMap.containsKey(resource)) {
return rclassProguardMap.get(resource);
} else {
return "";
}
} else {
if (ApkUtil.isRClassName(ApkUtil.getPureClassName(className))) {
return (ApkUtil.getPureClassName(className) + "." + fieldName).replace('$', '.');
}
}
}
}
}
return "";
}
UnusedAssetsTask
目的:检测出apk中未被使用的asset资源(代码实现仅覆盖了字符串常量的情况,会有遗留)。
@Override
public TaskResult call() throws TaskExecuteException {
try {
TaskResult taskResult = TaskResultFactory.factory(type, TaskResultFactory.TASK_RESULT_TYPE_JSON, config);
long startTime = System.currentTimeMillis();
File assetDir = new File(inputFile, ApkConstants.ASSETS_DIR_NAME);
//找到所有asset文件
findAssetsFile(assetDir);
generateAssetsSet(assetDir.getAbsolutePath());
Log.d(TAG, "find all assets count: %d", assetsPathSet.size());
//解析代码中的asset引用
decodeCode();
Log.d(TAG, "find reference assets count: %d", assetRefSet.size());
//移除被引用的资源
assetsPathSet.removeAll(assetRefSet);
JsonArray jsonArray = new JsonArray();
for (String name : assetsPathSet) {
jsonArray.add(name);
}
((TaskJsonResult) taskResult).add("unused-assets", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
private void generateAssetsSet(String rootPath) {
HashSet<String> relativeAssetsSet = new HashSet<String>();
for (String path : assetsPathSet) {
int index = path.indexOf(rootPath);
if (index >= 0) {
String relativePath = path.substring(index + rootPath.length() + 1);
//Log.d(TAG, "assets %s", relativePath);
relativeAssetsSet.add(relativePath);
if (ignoreAsset(relativePath)) {
Log.d(TAG, "ignore assets %s", relativePath);
//获取asset使用时的相对路径
assetRefSet.add(relativePath);
}
}
}
assetsPathSet.clear();
assetsPathSet.addAll(relativeAssetsSet);
}
private void readSmaliLines(String[] lines) {
if (lines == null) {
return;
}
for (String line : lines) {
line = line.trim();
// invoke-virtual {p0}, Lcom/ss/android/alog/App;->getAssets()Landroid/content/res/AssetManager;
//move-result-object v1
//const-string v2, "video"
//invoke-virtual {v1, v2}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;
//:try_end_13
//.catch Ljava/io/IOException; {:try_start_a .. :try_end_13} :catch_1a
//这个const-string判断不是很完善,只能判断写死的值
if (!Util.isNullOrNil(line) && line.startsWith("const-string")) {
String[] columns = line.split(",");
if (columns.length == 2) {
String assetFileName = columns[1].trim();
assetFileName = assetFileName.substring(1, assetFileName.length() - 1);
if (!Util.isNullOrNil(assetFileName)) {
//再判断这个常量是否在asset文件名集合中
for (String path : assetsPathSet) {
if (path.endsWith(assetFileName)) {
assetRefSet.add(path);
}
}
}
}
}
}
}
UnStrippedSoCheckTask
目的:检测出apk中未裁剪的so。
@Override
public TaskResult call() throws TaskExecuteException {
try {
...
if (libDir.exists() && libDir.isDirectory()) {
File[] dirs = libDir.listFiles();
for (File dir : dirs) {
if (dir.isDirectory()) {
File[] libs = dir.listFiles();
for (File libFile : libs) {
if (libFile.isFile() && libFile.getName().endsWith(ApkConstants.DYNAMIC_LIB_FILE_SUFFIX)) {
libFiles.add(libFile);
}
}
}
}
}
for (File libFile : libFiles) {
//判断是否裁剪
if (!isSoStripped(libFile)) {
Log.d(TAG, "lib: %s is not stripped", libFile.getName());
jsonArray.add(libFile.getName());
}
}
((TaskJsonResult) taskResult).add("unstripped-lib", jsonArray);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}
通过命令行判断so是否被裁剪
private boolean isSoStripped(File libFile) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(toolnmPath, libFile.getAbsolutePath());
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = reader.readLine();
if (!Util.isNullOrNil(line)) {
//Log.d(TAG, "%s", line);
String[] columns = line.split(":");
if (columns.length == 3 && columns[2].trim().equalsIgnoreCase("no symbols")) {
return true;
}
}
reader.close();
process.waitFor();
return false;
}
CountClassTask
目的:统计类的数量。
@Override
public TaskResult call() throws TaskExecuteException {
try {
TaskResult taskResult = TaskResultFactory.factory(type, TaskResultFactory.TASK_RESULT_TYPE_JSON, config);
long startTime = System.currentTimeMillis();
Map<String, String> classProguardMap = config.getProguardClassMap();
JsonArray dexFiles = new JsonArray();
for (int i = 0; i < dexFileList.size(); i++) {
RandomAccessFile dexFile = dexFileList.get(i);
DexData dexData = new DexData(dexFile);
dexData.load();
ClassRef[] defClassRefs = dexData.getInternalReferences();
Set<String> classNameSet = new HashSet<>();
for (ClassRef classRef : defClassRefs) {
String className = ApkUtil.getNormalClassName(classRef.getName());
if (classProguardMap.containsKey(className)) {
className = classProguardMap.get(className);
}
if (className.indexOf('.') == -1) {
continue;
}
classNameSet.add(className);
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("dex-file", dexFileNameList.get(i));
//Log.d(TAG, "dex %s, classes %s", dexFileNameList.get(i), classNameSet.toString());
Map<String, Set<String>> packageClass = new HashMap<>();
if (JobConstants.GROUP_PACKAGE.equals(group)) {
String packageName = "";
for (String clazzName : classNameSet) {
packageName = ApkUtil.getPackageName(clazzName);
if (!Util.isNullOrNil(packageName)) {
if (!packageClass.containsKey(packageName)) {
packageClass.put(packageName, new HashSet<String>());
}
//按package聚合
packageClass.get(packageName).add(clazzName);
}
}
JsonArray packages = new JsonArray();
for (Map.Entry<String, Set<String>> pkg : packageClass.entrySet()) {
JsonObject pkgObj = new JsonObject();
pkgObj.addProperty("package", pkg.getKey());
JsonArray classArray = new JsonArray();
for (String clazz : pkg.getValue()) {
classArray.add(clazz);
}
//单个package下的所有class
pkgObj.add("classes", classArray);
packages.add(pkgObj);
}
jsonObject.add("packages", packages);
}
dexFiles.add(jsonObject);
}
((TaskJsonResult) taskResult).add("dex-files", dexFiles);
taskResult.setStartTime(startTime);
taskResult.setEndTime(System.currentTimeMillis());
return taskResult;
} catch (Exception e) {
throw new TaskExecuteException(e.getMessage(), e);
}
}