Plug-in engineering R file thinning technical solution | JD Cloud technical team

With the development of business and version iteration, new business logic and new resources are continuously added to the client project, and the ensuing problem is that the size of the installation package becomes larger. In the early stage, various business modules are deleted by using useless resources, and the large image Compression or transfer to the cloud, AB experimental business logic offline or other means have achieved certain results in reducing the package size.

In the process of slimming down, we paid attention to the concept of slimming down R files. Currently, JD APP supports plug-in, including business plug-in projects and host projects. After analyzing the business plug-in package files, we found that in addition to conventional resources and codes, R class Files account for about 3% to 5% of the package volume. After analyzing the host project package files, R files also account for about 3%. After researching the feasibility of R file slimming and open source projects in the industry, we have explored a set of R file slimming technology solutions suitable for plug-in projects.

Theoretical basis - R file

The R file is the R.java file that we often deal with in our daily work. In the Android development specification, we need to put the resources used in the application into a specially named resource directory, and externalize the application resources so that they can be maintained separately. .

After externalizing the application resources, we can use the R class ID to access these resources in the project, and the R class ID is unique.

public class MainActivity  extends BaseActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

In the android apk packaging process, R class files are packaged and generated by aapt (Android Asset Packaging Tool). When generating R class files, resource files are compiled at the same time to generate resource.arsc files. The resource.arsc file is equivalent to a file Index table, the application layer code can access the corresponding resources through the R class ID.

Feasibility analysis of R file slimming

In the daily development stage, reference resources in the main project through R.xx.xx. After compilation, the constants corresponding to the R class references will be compiled into the class.

 setContentView(2131427356);

This change is called inlining, which is a mechanism of java (if a constant is marked as static final, the constant will be inlined into the code during java compilation, reducing the memory addressing of variables once).

In the non-main project, the R class resource ID is compiled into the class by reference, and no inlining will be generated.

 setContentView(R.layout.activity_main);

The reason for this phenomenon is caused by the AGP packaging tool. For details, you can check the processing process of the android gradle plugin on the R file.

Conclusion: The program can run after the R class id is inlined, but not all projects will automatically generate inline phenomenon. We need to use technical means to inline the R class id into the program at the right time. If you rely on R files again, you can delete the R files to achieve the purpose of reducing the size of the package while the application is running normally.

Plug-in engineering R file slimming practice

Formulate technical solutions

Currently JD Android client supports plug-in, the whole plug-in project includes public library (an aar project, used to store classes and resources shared by components and host), business plug-in (plug-in project is an independent project, compiled product Can run in the host environment), host (the main project, providing the operating environment). In order to prevent resource conflicts between the host and the plug-in during the plug-in process, the uniqueness of the resource is guaranteed by modifying the plug-in packageId. Since the public resource library and the host are relied on by many businesses, the evaluation of the impact of changes to these two projects involves a lot. The plug-ins are generally maintained by the business modules themselves, and there is no problem of being relied on. Therefore, the business plug-in module is first used for R-type slimming. practice.

After decompiling the package produced by the business plug-in project, it is found that the R class ID has no inline phenomenon, and the R class file has a certain size. After analyzing the R file in the package, it is found that the R file only contains the resources of the business itself. Public resource R classes that do not contain business dependencies.

public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
    this.b = paramLayoutInflater.inflate(R.layout.lib_pd_main_page, paramViewGroup, false);
    this.h = (PDBuyStatusView)this.b.findViewById(R.id.pd_buy_status_view);
    this.f = (PageRecyclerView)this.b.findViewById(R.id.lib_pd_recycle_view);}

Combined with the research and analysis of open source projects in the industry, try to formulate a technical solution that conforms to JD.com and give priority to completing the R-type ID inlining in the business plug-in and deleting the corresponding R file.

1. Collect the class files to be processed through the transform api

Transform is a way of manipulating bytecode provided by Android Gradle, which modifies .class files through a series of Transform processes before class is compiled into dex.

@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
//  通过TransformInvocation.getInputs()获取输入文件,有两种
//  DirectoryInpu以源码方式参与编译的目录结构及目录下的文件
//  JarInput以jar包方式参与编译的所有jar包
    allDirs = new ArrayList<>(invocation.getInputs().size());
    allJars = new ArrayList<>(invocation.getInputs().size());
    Collection<TransformInput> inputs = invocation.getInputs();
    for (TransformInput input : inputs) {
        Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
         for (DirectoryInput directoryInput : directoryInputs) {
               allDirs.add(directoryInput.getFile());
             }
            Collection<JarInput> jarInputs = input.getJarInputs();
         for (JarInput jarInput : jarInputs) {
                allJars.add(jarInput.getFile());
             }
     }
}

2. Analyze and process the collected .class files combined with the ASM framework

ASM is a class library for manipulating Java bytecode. Through ASM, we can easily modify .class files.

Prioritize the identification of R class files, access R.class files through ClassVisitor, read static constants in the files, and store temporary variables:

@Overridepublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {    //R类中收集 public static final int 对应的变量  if (JDASMUtil.isPublic(access) && JDASMUtil.isStatic(access) &&JDASMUtil.isFinal(access) &&JDASMUtil.isInt(desc)) {       jdRstore.addInlineRField(className, name, value);      }      return super.visitField(access, name, desc, signature, value);}

For non-R files, identify the R reference in the code through MethodVisitor, obtain the value corresponding to the reference, and replace the id value:

@Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.GETSTATIC) {
            //owner:包名;name:具体变量名;value:R类变量对应的具体id值
            Object value = jdRstore.getRFieldValue(owner, name);
            if (value != null) {
              //调用该api实现值替换
                mv.visitLdcInsn(value);
                return;
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

*Note: The above code is only part of the schematic code, unofficial plug-in code.

After introducing the R-type thin plug-in into the business module, the function of the business module can run normally, and the size of the plug-in package has been reduced by 3% to 5% to varying degrees.

public resource R class ID inline

In JD.com android client code, more resource files are concentrated in the public resource library, and the relative R files generated by the public library are also larger. After analyzing the contents of the compiled apk package, the R files of the public resource library Class files account for up to 3%.

The public library is packaged together with the host, and the R-type slimming plug-in is introduced during the packaging process of the host. The packaged apk is significantly reduced. Abnormal crash phenomenon, the crash type is Rx resource not found. The reason for the crash is analyzed as follows: the business plug-in code uses R resources in the public library, and the plug-in packaging process is independent of the host packaging. During the plug-in packaging process, only the inlining of the business module R class is completed, and public The inlining of resource R class, based on the above reasons, when the host package process completes the deletion and thinning of R class files, we will naturally report the problem that the public resource R class cannot be found during the process of running a business plug-in, resulting in a crash.

In order to solve this problem, the initial plan was to add a whitelist mechanism to keep all public resources used by business modules, but this idea was quickly overturned. The existence of public resources itself is to hope that each business module can directly refer to these resources. Instead of defining by yourself, if you keep it, there must be a large part of the resources that cannot be deleted, and the effect of weight loss will be greatly reduced.

Since the reserved solution is not suitable, the public resource R class id is also inlined into the code. As mentioned earlier, JD.com supports plug-in, and the entire plug-in solution is implemented based on the aura platform. We consulted the aura team, and then got a new entry point for the solution.

The aura platform has introduced the ability to fix public resource ids through aapt2 during the plug-in process. Under this capability, **the defined public resource ids will always be fixed (the public resource ids referenced in each business plug-in are the same), and The existing resources in the public resource library cannot be repeatedly defined by other modules, otherwise the previously defined resources will be overwritten. **Based on the above results and rules, we have improved the function of the previous R file slimming gralde plugin, and the public resources The R class id is inlined into the project.

Use the two parameters -stable-ids and -emit-ids of appt2 to realize the function of curing resource id, and name the cured ids file as shared_res_public.xml and store it in the public resource library. The business plug-in depends on the public resource library. In the process of packaging and compiling, aura will copy shared_res_public.xml to the specified location under the temporary compilation folder intermediates of the business project and participate in the packaging process of the business module. The format of the file content is as follows:

Modify the R file to slim down the gradle plugin code, read and identify this part of public resources from the specified location, store variables in the form of <name, id>, and replace the id of the public resources in the business module in the subsequent process.

public Map<String, String> parse() throws Exception {
        if (in == null) {
            return null;
        }
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(in);
        Element rootElement = doc.getDocumentElement();
        NodeList list = rootElement.getChildNodes();
        ......
        return resNode;
    }
}

The inline part of the R resource id code is as follows:

public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.GETSTATIC) {
            //优先从业务模块R类资源中查找
            Object value = jdRstore.getRFieldValue(owner, name);
            if (value != null) {
                mv.visitLdcInsn(value);
                return;
            }
           //从公共R类资源中查找
            value = getPublicRFileValue(name);
            if (value != null) {
                mv.visitLdcInsn(value);
                return;
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

After the solution is perfected, it is verified in combination with the Shangqing business plug-in. After the business details and the host have completed the inline thinning of the R file, the business functions of the Shangxiang module can be used normally without abnormal phenomena.

Considering that the R file inline slimming gradle plugin is introduced during the packaging and compiling phase, we also counted the impact on the packaging time after the introduction of the plugin. The data is as follows:

According to the data, the introduction of the R file slimming plug-in has no significant impact on the overall packaging time.

So far, based on the plug-in engineering R file slimming gradle plugin explored by Jingdong Mall, the development has been completed. At present, some business plug-in modules have been verified online. After the function is launched, we have also carried out crash observation and follow-up of user feedback in a timely manner. , there are no exceptions. Of course, developers have a variety of technical solutions for the purpose of reducing the size of the R file and reducing the package size. Relevant tools are involved in various stages of work, and can efficiently and effectively control the growth of package volume. If you have relevant suggestions and ideas on weight loss, you are welcome to discuss it together.

Reference article:

Gradle Plugin:

https://docs.gradle.org/current/userguide/custom_plugins.html

Gradle Transform:

https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform

APK build process:

https://developer.android.com/studio/build/index.html?hl=zh-cn#build-process

Author: Geng Leitian Innovation

Source: JD Cloud Developer Community

Guess you like

Origin blog.csdn.net/JDDTechTalk/article/details/131184424