Android solves the problem of index conflict of game distribution and package cutting resources

  • 2021-12-11 18:57:09
  • OfStack

Directory background 1. Introduction of public. xml 1. public. xml Where did this file come from?
2. What is the role of public. xml
3. Format of id in public. xml
2. Introduction of R class Relationship between R class and public. xml
3. The processing of R and public. xml in the process of packet cutting and fusion 1. Parse the value of public. xml when decompiling the mother package and save it. 2. When merging channel resources, merge public. xml in channel resources into public. xml (matrixPublic) in parent package. 3. Merge into the resources of new sdk. After covering R class, start to correct the value of R class
4. Summary

Background

In the process of game distribution and package cutting, we often encounter the problem of program abnormality caused by resource ID conflict in the process of merging resources of channel, R&D, publisher and three parties. This kind of problem can be solved by getIdentifier to avoid or modify conflict resource ID, but the cost is high. This paper aims to propose a solution to automatically deal with resource conflict in the process of package cutting

1. Introduction of public. xml

1. public. xml Where did this file come from?

This file is generated by apktool from the resources. arsc file in the apk package when decompiling apk.

Haven't seen resource. arsc? (Drag an apk to IDE by yourself)

2. What is the role of public. xml

publc. xml is used by aapt to fix the resource id when packaging resources. If the resource has a corresponding id in public. xml, the existing id will be used when packaging resources.

3. Format of id in public. xml

A total of 4 bytes with 32 bits, the first byte represents PackgeID, the second byte represents TypeID, and the last two bytes represent resource values

Usually the system resource PackageID is 01, and our own resource PackageID is 7f

TypeID, for example, attr is 01 and string is 02. But it is not fixed. It is not fixed that attr is 01. However, in public. xml, the byte 1 of the same type must be like 1, otherwise the recompilation will fail.

2. Introduction of R class

R class here has a knowledge point, library module generated in the R class members of the value is not constant, without final. The value of the R class generated by the app module is a constant value. Constant values are optimized when java is compiled, and the final output in the code is the constant value, not R. id. xxx. library will not be optimized because it is a variable, and R. id. xxx will be retained in the code

Relationship between R class and public. xml

In essence, it doesn't really matter. But since we will use R. id to find resources in the code, this is relevant. If you get id in the way of getIdentifier first, it's okay to delete R class.

After public. xml is packaged, it corresponds to the value in resources. arsc, and the resource value generates Java class, which is R class. That is to say, usually use R class, that is, use the index value inside to find the corresponding resource location in resources. arsc, and then load it.

3. The processing of R and public. xml in the process of packet cutting and fusion

In the process of package cutting, the R class belongs to the code and adopts the way of direct coverage, but the value of the R class generated by us will be different from that of the R class of the parent package.

The following cp refers to the game developer, that is, the access party of our SDK.

And public. xml is cp, why cp? Because cp builds the app project, R class is a constant value, if we change the existing value in public. xml in the mother package, and use it in the mother package, then gg will be used

Because the R class is a variable when used in library, the form of R. id. xxx is retained, and the solution is available. Correct the value in R class to correspond to public. xml, so that you can continue to use R. id. xxx happily.

Our package cutting process has several steps:

Decompile the parent package (referring 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 when decompiling the mother package and save it.


private void init() {
  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);
   }
  }
}

2. When merging channel resources, merge public. xml in channel resources into public. xml (matrixPublic) in parent package

Merge strategy:

a, channelPublic, but not matrixPublic, added to matrixPublic

For example, add the following data to matrixPublic


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

If the type already exists in the matrixPublic:

First, get PackageId+TypeId of attr in matrixPublic. In an public. xml file, PackageId + TypeId of the same type, such as attr, cannot be changed, otherwise the recompilation fails. So to add data, the PackageId+TypeId of the data needs to be corrected to the value of matrixPublic.

Secondly, the resource value cannot be duplicated with the existing resource value. Under normal circumstances, the value in public. xml is ordered generated by aapt. Here, the maximum value of attr type value in matrixPublic can be scanned, and then 1 is added as the id value of the newly added iconSrc

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

First of all, we should get what types have been occupied, that is, get TypeId in matrixPublic, which is orderly under normal conditions, get the largest TypeId, and add 1 as the starting value of the new Type. id value assigned to iconSrc

b, channelPublic, and matrixPublic, do not need to be processed, leaving the value in matrixPublic unchanged

3. Merge the resources into the new sdk, overwrite the R class, and then start to correct the value of the R class

Scan R class in PublicAndRHelper

Scan all R classes in smali code covering R class, except R $styleable class, because styleable stores the values of 1 array, and the rules are different.


/**
  *  Scan the in the code R Class 
  * @return
  */
 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.getName().equals("R.smali") || file.getName().startsWith("R$")) {
     // Filter out here styleable Documents 
     if (!file.getName().endsWith("R$styleable.smali")) {
      mRClassFileList.add(file.getAbsolutePath());
     }
    }
   }
  }
 }

Invoke the method in the corrective R class for every R class, and the corrective R class value is in the RValueHelper class

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

Note that instead of using replace (oldValue, newValue), the correction here should use the replacement line, because there is a problem after the new value also exists in the R class. For example, when a is replaced by b and b is replaced by c, a and b in R class are finally replaced by c

Secondly, the processing of styleable. When the scanned R is of attr type, it is judged whether there is styleable type. If there is, the correction made in attr is cached for correcting 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);
   //styleable Exist, then put attr File replaced value cache 
   if (RStyleAbleFile.exists()) {
    cacheMap = new HashMap<>();
   }
  }
  String rFileContent = FileUtil.read(RFilePath);
  // Find RFile Is each of the attributes in 1 Row 
  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));
    }
    // Replace to file contents 
    rFileContent = rFileContent.replace(line, ".field public static final " + name + ":" + matcher.group(2) + " = " + resetValue);
    if (cacheMap != null) {
     // The changed values are cached 
     cacheMap.put(matcher.group(3), resetValue);
    }
   }
  }
  FileUtil.write(RFilePath, rFileContent);
  if (cacheMap != null) {
   // Correct R$styleable Value of 
   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;
     }
    }
   }
  }
 }

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

4. Summary

In the game distribution industry, because the package cutting process is a merging process of multi-party code resources, resource conflicts often occur. This scheme is dedicated to optimizing the package cutting process and automatically solving the resource conflict problem. This scheme has applied for a patent, and is used in our actual business and runs stably

The above is Android to solve the problem of game distribution package resource index conflict in detail, more information about Android game distribution package resource index conflict please pay attention to other related articles on this site!


Related articles: