The author

Hello everyone, my name is Xiao Xin, you can also call me crayon xiao Xin 😊;

I graduated from Sun Yat-sen University in 2017 and joined the 37 mobile Game Android team in July 2018. I once worked in Jubang Digital as an Android development engineer.

Currently, I am the overseas head of android team of 37 mobile games, responsible for relevant business development; Also take care of some infrastructure related work.

background

In the process of game release and package cutting, it is common to encounter the problem of abnormal program caused by the resource ID conflict between the channel, the RESEARCH and development, and the publisher in the process of resource merger. Such problem can be solved by avoiding or modifying the conflicting resource ID through getIdentifier, but the cost is high. This paper aims to propose a solution of automatic resource conflict handling in packet cutting

1. Introduction to public.xml

Where did the public. XML file come from?

This file is generated when apkTool decommounts APK from the resources.arsc file in the APK package.

Haven’t seen the resource. Arsc? (Drag apK to IDE yourself)

2. What does public.xml do

Publc.xml is used by AAPT to package resources with fixed resource IDS. If the resource has a corresponding ID in public. XML, it is packaged with the existing ID.

3. The format of the ID in public

The first byte represents PackgeID, the second byte represents TypeID, and the last two bytes represent the resource value

The usual system resource PackageID is 01, while our own resource PackageID is 7F

TypeID, for example attr is 01 and string is 02. But it’s not fixed, it doesn’t have to be attR is 01. But in public.xml, the byte of the same type must be the same, otherwise the backcompile will fail.

2. Introduction to R class

The value of the R class generated by the Library module is not constant and does not have final values. The value of class R generated by the APP module is a constant value. Constant values are optimized during Java compilation, and the resulting code outputs constant values instead of r.I.D.xx. Library’s, since they are variables, will not be optimized and will keep r.i.D.xx in the code

Relationship between R class and public.xml

In essence, it doesn’t really matter. But since in the code we use R.ID to find the resource, it’s relevant. If you use getIdentifier to obtain the id first, you can delete the R class.

The public.xml package corresponds to the values in Resources.arsc, and the resource values generate Java classes, which are called R classes. Arsc = resources. Arsc = resources. Arsc = resources.

3. Processing of R class and public. XML in the process of packet cutting and fusion

In the process of package cutting, class R belongs to the code and is directly overwritten, but the value of class R generated by us will be different from that of the parent package.

Cp in the following refers to the developer of the game, which is the access party of our SDK.

Public. XML uses cp. Why cp? Class R is a constant value. If we change the value of public. XML in the parent package, it will be gg

Since the R class is used as a variable in the Library, the solution is to keep the form R.i.D.xx, correct the values in the R class to correspond to public.xml, and continue to use R.I.D.xx happily.

Our packet cutting process has several steps:

Decomcompile parent package (refers to party B accessing our SDK) ==== merge channel resources ==== merge resources into the new SDK (skip the process of developing and updating our SDK)

1. Parse the value of public. XML during decompilation and save it.

private void init(a) {
       List<Element> elements = mDocument.getRootElement().elements();
       for (Element element : elements) {
           String type = element.attribute(TYPE).getStringValue();
           String name = element.attribute(NAME).getStringValue();
           String id = element.attribute(ID).getStringValue();
           Map<String, String> typeMap = mTypeMap.get(type);
           if (typeMap == null) {
               typeMap = new HashMap<>();
               typeMap.put(name, id);
               mTypeMap.put(type, typeMap);
           } else{ typeMap.put(name, id); }}}Copy the code

When merging channel resources, merge public. XML from channel resources into public. XML from matrixPublic

Merge strategy:

A, channelPublic, matrixPublic, matrixPublic, matrixPublic

For example, add the following data to matrixPublic

<public type="attr" name="iconSrc" id="0x7f0200a8" />
Copy the code

If the type already exists in matrixPublic:

Attr in matrixPublic: PackageId+TypeId In a public. XML file, PackageId+TypeId of the same type such as attr cannot be changed; otherwise, backcompiling fails. Therefore, to add data, the PackageId+TypeId of the data needs to be corrected to the value of matrixPublic.

The values in public. XML are normally ordered by AAPT. You can scan for the maximum attr values in matrixPublic and add one as the id of the newly added iconSrc

If the type does not exist in matrixPublic (assuming the attr type does not exist in matrixPublic)

The first step is to get what TypeId is already occupied by a matrixPublic TypeId, which is normally ordered, and then get the maximum TypeId, plus one as the starting value for the new Type. The ID value assigned to iconSrc

B, channelPublic, matrixPublic, matrixPublic

3. Merge the resources into the new SDK and start correcting the values of R class after overwriting R class

Scan R classes in PublicAndRHelper

Scan all R classes in the smali code that covers R, except for R$styleable, because styleable holds some array values with different rules.

/** * Private void scannerRClass(String path) {File smaliFilePath = new File(path); for (File file : smaliFilePath.listFiles()) { if (file.isDirectory()) { scannerRClass(file.getAbsolutePath()); } else if (file. IsFile ()) {if (file. The getName () equals (" R.s Mali ") | | file. The getName (). The startsWith (" R $")) {/ / filters out styleable file here if (! file.getName().endsWith("R$styleable.smali")) { mRClassFileList.add(file.getAbsolutePath()); } } } } }Copy the code

For each R class call the correct R class method, the correct R class value is in the RValueHelper class

Policy: match the line to be corrected, get type, name. Find the corresponding value in public.xml and correct it.

Notice that we’re not going to replace (oldValue,newValue), we’re going to replace lines, because there’s a newValue that exists in the R class, and then there’s a problem with subsequent substitutions. Let’s say a is replaced by B, and B is replaced by C and eventually a and B in class R are replaced by C

The second is styleable processing. When R is an ATTR type, the system checks whether the styleable type exists. If so, the system caches the corrections made in the ATTR to correct the styleable.

public static void handle(String RFilePath, PublicXmlBean publicXmlBean) { File RFile = new File(RFilePath); String RStyleFilePath = ""; Map<String, String> cacheMap = null; if (RFile.getName().endsWith("R$attr.smali")) { RStyleFilePath = RFilePath.replace("R$attr", "R$styleable"); File RStyleAbleFile = new File(RStyleFilePath); If (rstyleablefile.exists ()) {cacheMap = new HashMap<>(); } } String rFileContent = FileUtil.read(RFilePath); ArrayList<String> lines = fileutil. readAllLines(RFilePath, ". Field public static final"); String regex = ".field public static final (.*):(.*) = (.*)"; for (String line : lines) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(line); if (matcher.find()) { String type = RFile.getName().replace("R$", "").replace(".smali", ""); String name = matcher.group(1); String resetValue = publicXmlBean.getValue(type, name); if (StringUtils.isEmpty(resetValue)) { resetValue = publicXmlBean.addValue(type, matcher.group(1)); } rFileContent = rfileconten.replace (line, ".field public static final " + name + ":" + matcher.group(2) + " = " + resetValue); if (cacheMap ! = null) {cachemap. put(matcher.group(3), resetValue); } } } FileUtil.write(RFilePath, rFileContent); if (cacheMap ! List<String> styleAbleLines = Fileutil.readalllines (RStyleFilePath); BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(RStyleFilePath)); for (String styleAbleLine : styleAbleLines) { for (String key : cacheMap.keySet()) { if (styleAbleLine.contains(key)) { styleAbleLine = styleAbleLine.replace(key, cacheMap.get(key)); } } bw.write(styleAbleLine); bw.newLine(); } } catch (IOException e) { e.printStackTrace(); } finally { if (bw ! = null) { try { bw.close(); } catch (IOException e) { bw = null; } } } } }Copy the code

At this point, we have corrected the values of the R class and public.xml

4, summary

In the game publishing industry, the package cutting process is a combination of multiple code resources, which often leads to resource conflicts. This scheme aims to optimize the packet cutting process and solve the resource conflict problem automatically. This scheme has been patented and used in our actual business and runs stably

Welcome to communicate

Students who have problems or need to communicate in the process can scan the TWO-DIMENSIONAL code to add friends, and then enter the group for problems and technical exchanges, etc.