The author
Hello everyone, my name is Xiao Jia; I graduated from Guangdong University of Technology with a bachelor’s degree in 20 years. I joined the 37 Mobile Games Android team in June 2020. At present, I am working on android related development for domestic game release.
background
In game publishing, we do channel cutting based on the following principles:
One of the steps in the package cutting process is to merge smali code. So far, what we have done is for a smali folder, which will cause the channel SDK to overwrite the parent package, some of them will not be covered. Such as:
In this way, the same SMali file will exist in multiple SmALI folders, resulting in the loading of dex, different dex files have the same class, so if there are differences in the class implementation of this class in different versions, it may lead to errors during invocation.
So we changed the whole packaging process:
(Autopackage blog guide:Juejin. Cn/post / 690752…
Optimized the automatic packaging process:
1. After automatically dividing tasks into mandatory stages, it will judge the current number of SMALI methods first. If it does not exceed, it will not perform subcontracting operations, but it will take more time to calculate
2. After the automatic subcontracting task is executed, the number of smALI methods will be judged again. If the number of smALI methods exceeds (because the current subcontracting logic is based on getting all classes indexed by the four components of the Application file as the content of the first SMALI, not directly based on the number of methods). Therefore, it is possible to exceed), and subcontract based on the number of methods.
In this way, there will be no 65535 problem when executing the package.
An important part of the process is to determine the number of methods. The original method is to scan the contents of the smali file and count the methods by the.end method field. But after a few tries, it’s not correct. Technically, it’s much smaller.
Counting plan
The official statement is simply the number of functions referenced (not repeated). In view of this statement, some implementations are carried out.
- The final conclusion: defined and uncalled methods + called methods, and only once
How do you determine if the number of methods you write is the same as the number of references you end up with when you generate dex? AndroidStudio’s APK Analyzer has a reference method count when analyzing a dex, which is the result of the calculation:
Method: After the reference method of dex in APK is obtained, it is converted into smALI file, and the algorithm results written by myself are compared to determine whether the algorithm is correct.
Algorithm idea:
Goal: Implement defined and uncalled methods + called methods, and keep them only once, counting the total number of times
Because they are reserved only once, so it is suitable to use the SET data interface, for repeated only once. In smali syntax: method calls are denoted by.invoke-xxxx:
invoke-static {v2, v3}, Lcom/netease/ntunisdk/base/UniSdkUtils; ->e(Ljava/lang/String; Ljava/lang/String;) V
Call e(String,String) static method of UniSdkUtils; return null
The method definition is.method XXXX. For example:
.method public static obj2Json(Lcom/netease/ntunisdk/base/AccountInfo;) Lorg/json/JSONObject;
Obj2Json (AccountInfo) returns JSONObject as a public method
Since different classes of methods may have the same signature, the class + method form is needed to distinguish them.
- The algorithm is:
Find the smali file and read it:
- Create a new set data structure that evaluates only once, as long as it has a defined method and is the same as the method being called.
- Find something that starts with. Class and take the current class name: Lcom/test/test_class;
- Find content that begins with. Method and translate it into the class name + “->” + function name + field type + return format
.method public constructor ()V 的 翻 译 : Lcom/test/test_class; ->()V adds the string to the set data structure
- Find content that starts with.invoke- and leave it there
Lcom/test/test_class; ->()V adds the string to the set data structure
Code implementation
/* * how many smali references are there? Public static Boolean dfsByStack(String path,int sum,List<String> List){HashSet<String> HashSet = new HashSet<String>(70000); int current = 0; File file = new File(path); if (! file.exists()) return false; Stack<String> stack = new Stack<>(); stack.push(path); while (! stack.isEmpty()){ String path1 = stack.pop(); File file1 = new File(path1); if (file1.exists()){ if (file1.isFile()){ String className = ""; List<String> lines = FileUtil.readAllLines(file1.getAbsolutePath()); if (lines == null) continue; for (String line : lines){ line = trimStart(line); If (line.startswith (".class")){String[] content = line.split(" "); className = content[content.length-1]; If (line.startswith (".method")){String[] content = line.split(" "); String method = content[content.length-1]; hashSet.add(className + "->" + method); } //3 Find the call method if (line.startswith ("invoke-")){String[] content = line.split(" "); String method = content[content.length-1]; hashSet.add(method); } } if (hashSet.size() > sum){ System.out.println("hashSet: " + hashSet.size()); System.out.println("current: " + current); return true; } current = hashSet.size(); if (list ! = null) { if (current <= sum /2) { list.add(file1.getAbsolutePath()); } } } if (file1.isDirectory()){ for (File path2: file1.listFiles()){ stack.push(path2.getAbsolutePath()); } } } } System.out.println("current: " + current); return false; }Copy the code
summary
This blog mainly explains some problems in the implementation of baling machine before, after improving the method, optimize the automatic subcontracting, explains the realization of method counting in dex file, and gives the code implementation in the context of SMALI.