ResProguardCheckTask

Objective: To determine whether apK performs resource obfuscation.

@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()); // add("hasResProguard", true); // 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); . }Copy the code

FindNonAlphaPngTask

Objective: To detect PNG files with no transparency (JPG should be used instead, which will take up less space)

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); // No alpha information 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())); } } } } }Copy the code

MultiLibCheckTask

Purpose: Check whether multiple folders exist in the lib folder.

@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); }}Copy the code

UncompressedFileTask

Purpose: Compare the size of each entry in the APK compression package after compression and before compression; If the sizes are the same, the files are not compressed.

@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 ()) {// size comparison 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); }}Copy the code

CountRTask

Objective: To measure the number of R files.

@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); / / identify R file if (pureClassName endsWith (" R ") | | "R". The 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); }}Copy the code

DuplicateFileTask

Objective: To determine whether identical files exist in APK by calculating MD5.

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)); }} // md5map.get (md5).add(filename); }}}}Copy the code
@Override public TaskResult call() throws TaskExecuteException { ... . for (Pair<String, Long> entry : 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; }Copy the code

MultiSTLCheckTask

Objective: To determine whether so has multiple copies of STL standard library.

@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); }}Copy the code
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; }Copy the code

UnusedResourcesTask

Purpose: Detects unreferenced resources in code and resource files.

@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 ()); // addAll declared resources. Log.d(TAG, "find resource declarations %d items.", unusedResSet.size()); // Find all the resources used in the code decodeCode(); Log.d(TAG, "find resource references in classes: %d items.", resourceRefSet.size()); // Find the resource referenced in all resources decodeResources(); Log.d(TAG, "find resource references %d items.", resourceRefSet.size()); Unusedresset.removeall (resourceRefSet); 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); }}Copy the code
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]); Field rClassProGuardMap. put(afterClass.replace('$', '.') + "." + value, afterclass.replace ('$', '.') + "." + value, ApkUtil.getPureClassName(beforeClass).replace('$', '.') + "." + field[1]); } } } } } line = bufferedReader.readLine(); } } finally { bufferedReader.close(); }}}Copy the code
Private void readResourceTxtFile() throws IOException {// Read r.t_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)) {// resource id resourceName mapping 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 mapping styleableMap.put(resourceName, attrReferences); } } } line = bufferedReader.readLine(); } } finally { bufferedReader.close(); }}Copy the code

Parse smali code in dex file:

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); }}}}Copy the code
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()); // Get the resource name from the 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) {/ / access to resources name 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); } } } } } } }Copy the code
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 ""; }Copy the code

UnusedAssetsTask

Purpose: To detect unused asset resources in APK (there is a legacy if the code implementation only overwrites string constants).

@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); FindAssetsFile (assetDir); generateAssetsSet(assetDir.getAbsolutePath()); Log.d(TAG, "find all assets count: %d", assetsPathSet.size()); // Parse the asset reference in the code decodeCode(); Log.d(TAG, "find reference assets count: %d", assetRefSet.size()); // Remove the referenced resource 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); }}Copy the code
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); // Get assetrefset.add (relativePath); // Get assetrefset.add (relativePath); } } } assetsPathSet.clear(); assetsPathSet.addAll(relativeAssetsSet); }Copy the code
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 (! IsNullOrNil (assetFileName)) {for (String path: assetsPathSet) { if (path.endsWith(assetFileName)) { assetRefSet.add(path); } } } } } } }Copy the code

UnStrippedSoCheckTask

Objective: To detect untrimmed SO in APK.

@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); }}Copy the code

Determine whether so is clipped by the command line

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; }Copy the code

CountClassTask

Purpose: To count the number of classes.

@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>()); } // aggregate 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); } 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); }}Copy the code