插件化工程R文件瘦身技术方案 | 京东云技术团队

news2024/11/18 15:43:25

随着业务的发展及版本迭代,客户端工程中不断增加新的业务逻辑、引入新的资源,随之而来的问题就是安装包体积变大,前期各个业务模块通过无用资源删减、大图压缩或转上云、AB实验业务逻辑下线或其他手段在降低包体积上取得了一定的成果。

在瘦身的过程中我们关注到了R文件瘦身的概念,目前京东APP是支持插件化的,有业务插件工程、宿主工程,对业务插件包文件进行分析,发现除了常规的资源及代码外,R类文件大概占包体积的3%~5%左右,对宿主工程包文件进行分析,R类文件占比也有3%左右。我们先后在对R类文件瘦身的可行性及业界开源项目进行调研后,探索出了一套适用于插件化工程的R文件瘦身技术方案。

理论基础—R文件

R文件也就是我们日常工作中经常打交道的R.java文件,在Android开发规范中我们需要将应用中用到的资源分别放入专门命名的资源目录中,外部化应用资源以便对其进行单独维护。

外部化应用资源后,我们可在项目中使用R类ID来访问这些资源,且R类ID具有唯一性。

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

在android apk打包流程中R类文件是由aapt(Android Asset Packaing Tool)工具打包生成的,在生成R类文件的同时对资源文件进行编译,生成resource.arsc文件,resource.arsc文件相当于一个文件索引表,应用层代码通过R类ID 可以访问到对应的资源。

R文件瘦身的可行性分析

日常开发阶段,在主工程中通过R.xx.xx的方式引用资源,经过编译后R类引用对应的常量会被编译进class中。

 setContentView(2131427356);

这种变化叫做内联,内联是java的一种机制(如果一个常量被标记为static final,在java编译的过程中会将常量内联到代码中,减少一次变量的内存寻址)。

非主工程中,R类资源ID以引用的方式编译进class中,不会产生内联。

 setContentView(R.layout.activity_main);

产生这种现象的原因是AGP打包工具导致的。具体细节,大家可以去查阅一下android gradle plugin在R文件上的处理过程。

结论:R类id内联后程序可运行,但并非所有的工程都会自动产生内联现象,我们需要通过技术手段在合适的时机将R类id内联到程序中,内联完成后,由于不再依赖R类文件,则可以将R类文件删除,在应用正常运行的同时,达到包瘦身目的。

插件化工程R文件瘦身实战

制定技术方案

目前京东Android客户端是支持插件化的,整个插件化工程包含公共库(是一个aar工程,用来存放组件和宿主共用的类和资源)、业务插件(插件工程是一个独立的工程,编译产物可以运行在宿主环境中)、宿主(主工程,提供运行环境)。在插件化的过程中为了防止宿主和插件资源冲突,通过修改插件packageId保证了资源的唯一性。由于公共资源库、宿主是被很多业务依赖,对这两个项目进行改动评估影响涉及比较多,插件一般都是业务模块自行维护,不存在被依赖问题,所以先在业务插件模块进行R类瘦身实践。

对业务插件工程打出的包进行反编译以后,发现R类ID无内联现象,且R类文件具有一定的大小,对包内的R文件进行分析,发现R文件中仅包含业务自身的资源,不包含业务依赖的公共资源R类。

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);}

结合对业界开源项目的调研分析,尝试制定符合京东商城的技术方案并优先在业务插件内完成R类ID内联并删除对应的R文件。

1.通过transformapi 收集要处理的class文件

Transform 是 Android Gradle 提供的操作字节码的一种方式,它在 class 编译成 dex 之前通过一系列 Transform 处理来实现修改.class文件。

@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.对收集到的.class文件结合ASM框架进行分析处理

ASM是一个操作Java字节码的类库,通过ASM我们可以方便对.class文件进行修改。

优先识别R类文件,通过ClassVisitor访问R.class文件,读取文件中的静态常量,进行临时变量存储:

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

非R类文件,通过MethodVisitor识别到代码中的R类引用,获取引用对应的值,进行id值替换:

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

*注:以上代码仅为部分示意代码,非正式插件代码。

在业务模块引入R类瘦身插件后,业务模块功能可正常运行,且插件包大小均有3%~5%不同程度的减少。

公共资源R类ID内联

由于在京东android客户端代码中,更多的资源文件集中在公共资源库中,相对的公共库生成的R类文件也更大,对编译后的apk包内容进行分析后,公共资源库的R类文件占比高达3%。

公共库跟随宿主一起打包,在宿主打包过程中引入R类瘦身插件,打包后的apk有明显的减小,手机安装apk后启动首页正常展示无问题,但在打开某些业务插件时,会有异常闪退现象,崩溃类型为R.x resource not found。对崩溃原因分析如下:业务插件代码中使用了公共库中的R类资源、插件打包流程独立于宿主打包,在插件打包的过程中仅完成了业务模块R类的内联,并没有考虑到公共资源R类的内联,基于上述原因当宿主打包过程完成R类文件删除瘦身后,我们在运行某业务插件的过程中,自然就会报公共资源R类找不到的问题从而产生崩溃。

为了解决这个问题一开始的方案设想是增加白名单机制,keep住所有被业务模块使用的公共资源,但很快这个想法就被推翻,公共资源存在本身就是希望各个业务模块直接引用这部分资源,而不是自己定义,如果keep住的话,必然有很大一部分的资源无法删减,瘦身的效果会大打折扣。

既然保留的方案并不合适,那就将公共资源R类id也内联到代码中去。前面提到京东是支持插件化的,整个插件化方案是基于aura平台实现的,我们向aura团队进行了咨询,然后get到了新的方案切入点。

aura平台在插件化的过程中已通过aapt2引入了公共资源id固定的能力,在该能力下,**已定义的公共资源id会一直固定(各个业务插件中引用的公共资源id一致),且公共资源库中已有的资源不可被其他模块重复定义,否则会覆盖之前已定义好的资源,**基于上述的结果和规则,我们对之前的R文件瘦身gralde plugin功能进行完善,将公共资源的R类id 内联到项目中。

利用appt2的-stable-ids和-emit-ids两个参数实现固化资源id的功能,并将将固化后的ids文件命名为shared_res_public.xml存储在公共资源库中,业务插件依赖公共资源库,在打包编译的过程中aura会将shared_res_public.xml复制到业务工程临时编译文件夹intermediates下的指定位置并参与业务模块的打包过程中,其文件内容格式如下:

修改R文件瘦身gradle plugin 代码,从指定位置读取并识别这部分公共资源,按照<name,id>的形式进行变量存储,并在后续过程中对业务模块中的公共资源部分进行id替换。

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;
    }
}

R类资源id内联部分代码如下:

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);
    }

该方案完善后,结合商详业务插件进行了验证,在商详及宿主均完成R文件内联瘦身后,商详模块业务功能可正常使用,无异常现象。

考虑到R文件内联瘦身gradle plugin是在打包编译阶段引入的,我们也统计了一下引入该插件以后对打包时长的影响,数据如下:

结合数据来看,引入R文件瘦身插件后对整体打包时长并无显著影响。

至此,基于京东商城探索的插件化工程R文件瘦身gradle plugin就开发完成,目前已在部分业务插件模块进行了线上验证,在功能上线以后我们也及时的进行了崩溃观测以及用户反馈的跟进,暂无异常问题。当然围绕R文件瘦身缩减包体积这个目的,开发人员有各种各样的技术方案,上述方案不一定适用于所有的客户端开发体系,另外后续也将围绕包瘦身这一常态事务建设一系列的相关工具,介入工作当中的各个阶段,高效、有效的控制包体积的增长,如大家在瘦身方面有相关建议和想法也欢迎大家来一起讨论。

参考文章:

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 构建流程:

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

作者:耿蕾 田创新

来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Window域控环境之账号误删恢复

文章目录 背景信息问题分析操作步骤 文章内容已做脱敏处理 背景信息 8&#xff1a;30&#xff0c;收到联络反馈客户误删除部门领导域控账户&#xff0c;希望紧急实施VM整机恢复工作。收到联络时&#xff0c;我是觉得这个事情挺严重的。毕竟现在域控账号是企业里面重要的身份与…

深度学习数据处理中,标量、向量、张量的区别与联系

计算机中的标量机是指只是一个数一个数地进行计算的加工处理方法&#xff0c;区别于向量机能够对一批数据同时进行加工处理。标量机比向量机的运算速度慢&#xff0c;因此&#xff0c;向量机更适合于演算数据量多的大型科学、工程计算问题。 计算机可以进行数值计算&#xff0c…

5.2.11 IP分组的转发(二)IP分组转发算法

5.2.11 IP分组的转发&#xff08;二&#xff09;IP分组转发算法 我们前面已经了解了路由器的结构以及直接交付和间接交付的概念&#xff0c;明白了路由器会根据路由协议生成路由表再根据路由表生成转发表&#xff0c;当路由器收到一个待转发的IP分组以后&#xff0c;会根据分组…

Background-1 基础知识 sqli-Labs Less1-Less-4

文章目录 一、Less-1二、Less-2三、Less-3四、Less-4总结 一、Less-1 http://sqli:8080/Less-1/?id1在第一关我们可以尝试增加一个单引号进行尝试 http://sqli:8080/Less-1/?id1错误显示如下&#xff1a; near 1 LIMIT 0,1 at line 1推测语法的结构 select *from where **…

2009年iMac装64位windows7

单位领导会花屏的iMac&#xff08;24寸 2009年初版&#xff09;我捡来用&#xff0c;应该大约是在2020年安装了32位windows7&#xff0c;发现不安装显卡驱动便不会花屏死机&#xff0c;于是就当简单的上网机用着&#xff0c;毕竟iMac的显示屏还是蛮不错的。现在要使用的1个软件…

linux中那些常用好玩的命令

前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文是专栏【linux基本功-基础命令实战】的第66篇文章&#xff0c;今天要分享是多个命令&#xff0c;在工作中不常用&#xff0c;但好玩。 专栏地址&#xff1a;[linux基本功-基础命令专栏] &#xff0c…

华为OD机试真题B卷 Java 实现【二叉树的所有路径】,附详细解题思路

一、题目描述 给定一个二叉树&#xff0c;返回所有从根节点到叶子节点的路径。 说明: 叶子节点是指没有子节点的节点。 二、思路与算法 最直观的方法是使用深度优先搜索。在深度优先搜索遍历二叉树时&#xff0c;我们需要考虑当前的节点以及它的孩子节点。 如果当前节点不是…

DAY 77 [ Ceph ] 基本概念、原理及架构

前言 在实现容器化的初期&#xff0c;计划使用 Ceph 作为容器的存储。都说存储是虚拟化之母&#xff0c;相对容器来说&#xff0c;存储也起到了至关重要的作用。 选用 Ceph 作为容器化存储理由如下&#xff1a; 方便后期横向扩展&#xff1b;Ceph能够同时支持快存储、对象存…

MM32F3273G8P火龙果开发板MindSDK开发教程15 - 获取msa311加速器的方向改变事件

MM32F3273G8P火龙果开发板MindSDK开发教程15 - 获取msa311加速器的方向改变事件 1、功能描述 类似手机里横屏竖屏检测&#xff0c;当方向发生变化时&#xff0c;横屏竖屏自动切换。 当msa311方向改变时&#xff0c;会产生中断&#xff0c;然后从寄存器Reg 0x0C(Orientation _…

图解LeetCode——20. 有效的括号

一、题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类…

i.MX RT1010跨界MCU调试利器(FreeMASTER上手体验)

FreeMASTER是一款基于PC的免费工具&#xff0c;用于可视化和调试嵌入式实时应用程序。它可以帮助开发人员快速实现深入嵌入式系统的数据测试和调试&#xff0c;它为嵌入式系统设计师提供了一个强大的、可视化的调试环境&#xff0c;在调试、验证和追踪实时应用程序时尤其有用。…

Atair 柱状比例图

如何熟练掌握可视化库和应对使用过程的疑难问题&#xff1f; 基本用法不妨访问 GeeksforGeeks 疑难问题优先搜索 https://stackoverflow.com 尽量使用官方文档&#xff1a; numpy的学习访问 https://numpy.org/doc/stable/user/index.html 例如&#xff1a; 一则 altair 使用过…

@antv/g2plot 特殊 散点图 x轴为category 调整了legend 的marker

下面代码演示了如何使用 antv/g2plot 创建一个散点图&#xff0c;并对其进行基本的样式和布局配置。 具体来说&#xff0c;代码中的 data 数组定义了散点图的数据系列&#xff0c;每个数据对象包含了分类、值和 y 轴字段三个属性。而 cateMap 对象则定义了每个分类对应的颜色和…

玩转ChatGPT:名单排序

一、写在前面 最近在文秘工作中&#xff0c;碰到一个名字排序的问题&#xff0c;大概的规则&#xff1a; &#xff08;1&#xff09;按照第一个汉字的首字母的英文单词排序&#xff0c;从A-Z&#xff1b; &#xff08;2&#xff09;若第一个字的首字母一致&#xff0c;则比较…

【920信号与系统笔记】第三章 连续信号的正交分解

连续信号的正交分解 3.1引言3.3信号表示为傅里叶级数(FS)三角傅里叶级数1. 本质展开式1展开式2展开条件-狄利克雷条件分量概念补充 指数傅里叶级数使用条件形式1&#xff08;按连续信号的正交分解定义展开&#xff09;形式2&#xff08;由三角函数形式的傅里叶级数推导&#xf…

力扣笔记(每日随机一题)——最佳买卖股票时机含冷冻期

问题&#xff08;中等&#xff09; 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票&#xff09;: 卖出股票后&a…

Unity HybridCLR + Xlua + Addressable 要点记要

接入缘由 老工程原本是C#&#xff0c;想做热更&#xff0c;于是接入了Xlua和Addressable。由于工程老&#xff0c;人手也不够&#xff0c;只是新代码使用Xlua&#xff0c;老功能&#xff08;尤其是核心战斗还是C#&#xff09;。大半年后觉得并不能达到预期需求。于是通过再接入…

vue-element-admin项目学习笔记(4)路由分析二:动态路由及permission.js

路由模块非常重要&#xff0c;自己基于这个框架进行开发&#xff0c;这个必须吃透&#xff01;&#xff01; 前情回顾&#xff1a; vue-element-admin项目学习笔记&#xff08;1&#xff09;安装、配置、启动项目 vue-element-admin项目学习笔记&#xff08;2&#xff09;main.…

改KendoReactUI组件,实现多个子元素的样式不一样

before after 问题描述 使用KendoReact RadioButton 组件&#xff0c;当用户选中当前项时需要显示不同颜色&#xff0c;试图通过找到KendoUI中对应的class name&#xff0c;然后修改这个class name下面的css来达到目的&#xff0c;但不起作用解决办法 用js控制style核心代码 …

华为OD机试之查找众数及中位数(Java源码)

查找众数及中位数 题目描述 众数是指一组数据中出现次数量多的那个数&#xff0c;众数可以是多个。 中位数是指把一组数据从小到大排列&#xff0c;最中间的那个数&#xff0c;如果这组数据的个数是奇数&#xff0c;那最中间那个就是中位数&#xff0c;如果这组数据的个数为偶…