HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

滴滴开源 DroidAssist : 轻量级 Android 字节码编辑插件

  • 2019-09-16
  • 本文字数:2609 字

    阅读完需:约 9 分钟

滴滴开源 DroidAssist : 轻量级 Android 字节码编辑插件

近日,滴滴发布的开源项目 DroidAssist ,提供了一种简单易用、无侵入、配置化、轻量级的 Java 字节码操作方式,只需要在 XML 配置中添加简单的 Java 代码即可实现编译期对 Class 文件的动态修改。


DroidAssist 和其他 AOP 方案不同,它提供了一种简单易用、无侵入、配置化、轻量级的 Java 字节码操作方式,你不需要 Java 字节码的相关知识,只需要在 XML 配置中添加简单的 Java 代码即可实现编译期对 class 文件的动态修改,同时不需要引入其他额外的依赖。

起源

作为大型 APP 的代表,滴滴出行乘客端集成了较多的业务线,包含了大量的依赖库,每个版本都有多个团队向乘客端集成大量的代码,而且这些代码都是难以直接追溯到源码的,同时乘客端还有用户量大,日活高,迭代快等特点,这些情况对乘客端的开发和维护形成很大的挑战,主要体现在:问题防范难度大、问题规模大、后期维护成本高。


2018 年 5 月,乘客端团队进行卡顿专项优化, 其中有个问题是:由于安卓系统 SharedPreferences 自身机制,当频繁调用 SharedPreferences.apply() 方法时,可能会出现由 QueuedWork.waitToFinish() 造成的卡顿和 ANR。主要原因是系统在 Activity 的 onPause、onStop,以及 Service 的 start 和 stop 生命周期时会执行阻塞等待 QueuedWork 清空,推测系统是为了保证持久化成功率,从而确保用户离开组件之前完成 SharedPreferences 的文件写入。


分析原因之后,我们认为,乘客端 APP 相对处于单一的进程环境,去掉这个持久化阻塞也是可以的。为了解决这个问题,我们决定对系统的 SharedPreferences 进行改造,实现我们自己的 SharedPreferences。


但是随之而来的问题是,我们自定义的 SharedPreferences 怎么以最小的成本接入到乘客端呢?很容易想到以下两种方案:


  • 修改所有调用 Context.getSharedPreferences() 的代码,返回我们自己的 SharedPreferences 对象,缺点:改动太多,工作量太大,修改、还原成本太高。

  • 所有的 Application、Activity、Service 类都从统一的的 Base 基类派生,在基类中重写 getSharedPreferences 方法返回自定义 SharedPreferences 对象,和方法一相比,此方法代码改动较小,但是也存在是无法修改第三方库,而且工作量也比较大,修改、还原成本也很高的问题。


以上两种方式都具有较大的侵入性,会涉及到大量的源码以及依赖库的代码改动,后期维护和升级成本也比较高,为了寻找更加理想的解决方案,我们希望找到一种无侵入的 Mock 工具,能做到不修改代码就能 Mock 所有 getSharedPreferences()方法的调用返回结果,初步有如下两种实现思路:


  • Hook:Hook 技术需要一直处理各种厂商和机型的兼容性问题,有较大的稳定性风险。

  • AOP:AOP 类框架在编译期实现字节码操作,比较成熟稳定,可以考虑采用,但是经过分析发现,现有的 AOP 框架包括 AspectJ 并不能实现我们需要的 Mock 功能。


类似 SharedPreferences 替换这样的需求还有很多,于是我们决定自己开发一个 Android 平台 Mock 工具,经过调研之后,我们确定了字节码修改的技术方向,通过修改字节码实现这样的需求,由此 DroidAssist 应运而生。


项目地址:https:// github. com/didi/DroidAssist

示例

下面例子是背景中提到的 SharedPreferences 改造,添加如下 DroidAssist 配置,在项目编译后,所有调用 Context.getSharedPreferences() 的代码,将全部会被修改为返回自定义的 SharedPreferences 实例的代码:


1 <Replace>2     <MethodCall>3DroidAssist4<Source>android.content.SharedPreferences android.content.Context.getS5haredPreferences(java.lang.String,int)</Source>6    <Target>{$_= com.didi.quicksilver.QuicksilverPreferencesHelper.getShar7edPreferences($0,$$);}</Target>8  </MethodCall>9</Replace>
复制代码


处理前的 class:


1public class MainActivity extends Activity {2@Override3    protected void onCreate(Bundle savedInstanceState) {4        super.onCreate(savedInstanceState);5        SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);6} }
复制代码


处理后的 class:


1public class MainActivity extends Activity {2    protected void onCreate(Bundle savedInstanceState) {3        super.onCreate(savedInstanceState);4        SharedPreferences sp = PreferencesHelper.getSharedPreferences(this5, "test", MODE_PRIVATE); // The target method return custom SharedPreferen6ces.7} }
复制代码


具体的使用方式及原理可参见 DroidAssist WIKI 。

特性

经过不断的打磨完善,DroidAssist 已经从最开始的 Mock 工具扩展成为具有完整 AOP 框架功能的工具,有如下特性。

简单易用

采用灵活的配置化方式,使用者只需要依赖一个插件,然后在配置文件中定义字节码处理方式,DroidAssist 就可以根据配置文件处理项目中所有的 class 文件。处理过程以及处理后的代码中都不需要添加额外的依赖,并且不会修改原始代码行号。

丰富的字节码处理功能

除了解决我们最初遇到的代码替换问题外,还扩展了其他的 AOP 功能,目前有 4 类 28 种代码修改方式。



  • 替换:把指定位置代码替换为指定代码

  • 插入:在指定位置的前后插入指定代码

  • 环绕:在指定位置环绕插入指定代码

  • 增强

  • TryCatch 对指定代码添加 try catch 代码

  • Timing 对指定代码添加耗时统计代码

简单易用

支持增量构建,处理速度快,只占用很少的构建时间。

Q&A

1. DroidAssist 可以实现什么功能?


DroidAssist 可以轻易实现诸如代码替换,代码插入等功能,滴滴出行 APP 利用 DroidAssist 实现了日志输出替换,系统 SharedPreferences 替换,SharedPreferences commit 替换为 apply,Dialog 展示保护,getDeviceId 接口替换,getPackageInfo 接口替换,getSystemService 接口替换,startActivity 保护,匿名线程重命名,线程池创建监控,主线程卡顿监控,文件夹创建监控,Activity 生命周期耗时统计,APP 启动耗时统计等功能。


2. DroidAssist 和 AspectJ 有什么区别?


DroidAssist 采用配置化方案,编写相关配置就可以实现 AOP 的功能,可以完全不用修改 Java 代码;DroidAssist 在使用上使用比较简单,不需要复杂的注解配置;DroidAssist 可以比较方便的实现 AspectJ 不容易实现的代码替换功能。一般情况下使用 DroidAssist 可以完成大部分功能,较复杂情况可以和 AspectJ 配合使用。


本文转载自公众号滴滴技术(ID:didi_tech)。


原文链接:


https://mp.weixin.qq.com/s/KYkazdohwIUse5uM37qPiQ


2019-09-16 22:291059

评论

发布
暂无评论
发现更多内容

搞不懂云原生

二哥不再迷茫

浪潮云洲荣获两项工业自动化及数字化行业年度大奖 inspur浪潮云 昨天

云计算

重磅来袭,虚拟化技术分类

hanaper

如何让文件共享 SDK 支持使用 Uri 上传文件

ZEGO即构

文件存储 分区存储 文件共享

目睹阿里技术官写的Tomcat架构笔记后,瞬间觉得自己是渣渣

Java 架构 面试 程序人生 编程语言

如何做一场有趣又高效的迭代回顾会议?

万事ONES

Scrum 敏捷 回顾会

用Python绘制专业的K线图【含源代码】

恒生LIGHT云社区

金九银十不要怕!有了腾讯这本2021年最新Java面试手册,offer手到擒来!

Java 程序员 架构 面试 后端

手把手教你使用Studio Lite + Digtal car!助力智能汽车场景、轻应用开发更轻松!

SOA开发者

图谱相关技术在风控反作弊中的应用和探索

百度Geek说

后端 软件架构

音视频终端引擎优化实践

百度开发者中心

最佳实践 音视频 实践案例 智能视频 行业深度

拥抱开源,共建生态 - 开源生态与效能提升专场 | CIF 精彩看点

CODING DevOps

DevOps 研发效能 腾讯云 CIF 峰会 开源生态

智能网联汽车行业信息安全现状与威胁

SOA开发者

华为云带你探秘Xtrabackup备份原理和常见问题分析

华为云数据库小助手

GaussDB 华为云数据库 MySQL 数据库 GaussDB(for MySQL)

智能大数据专场,百度智能云带来智能大数据产品架构全景图

百度大脑

人工智能 大数据

四面字节跳动(高级开发岗):分布式+中间件+TCP+JVM+Hashmap

Java 编程 架构 面试 计算机

从工具、工具箱到数字化软件工厂——DevOps 设计理念与工程实践专场 | CIF 精彩看点

CODING DevOps

DevOps 数字化 研发工具 腾讯云 CIF 峰会

艾瑞发布《2021年中国企业级 SaaS 行业研究报告》,ONES 入选典型厂商案例

万事ONES

项目管理 SaaS 协同办公

史上最全Java高频面试合集,命中率高达95%

Java 程序员 架构 面试 后端

AlibabaP9力荐!最新出品1297页JDK源码+并发核心原理解析小册

Java 架构 面试 程序人生 编程语言

超十年渗透专家总结出636页渗透测试全笔记,100课时一次讲清

Java 架构 面试 程序人生 编程语言

掘金热榜第一!阿里P8用近十个月整理出来999页Java岗核心笔记限时开源!

Java 架构 面试 程序人生 编程语言

阿里巴巴架构师十年整理出的JavaSpringBoot核心文档,真是太全了

Java 架构 面试 程序人生 编程语言

提高工作效率的三种方法

石云升

团队管理 管理 引航计划 内容合集 9月日更

互斥锁、自旋锁、读写锁...理清它们的区别和应用

行云创新

云计算 编程 开发 应用

【新品尝鲜】OCR磅单识别上线邀测,为货运物流提速增效

百度大脑

人工智能 OCR

行云创新:云原生技术助力企业数字化转型

行云创新

技术 云原生 转型 数字化 平台

不得不聊一聊英语爱好者的单词杂货铺了

小匚

随笔杂谈 生活记录 英语 大学英语四六级 单词

使用 grpcurl 通过命令行访问 gRPC 服务

AlwaysBeta

golang gRPC

东软熙康、百度、京东方,互联网医院的三个典型样本

海比研究院

快手、知乎等平台严厉打击“病媛”炒作行为:自媒体行业不能被流量裹挟

石头IT视角

滴滴开源 DroidAssist : 轻量级 Android 字节码编辑插件_开源_江义旺_InfoQ精选文章