写点什么

滴滴开源 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:291106

评论

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

“云创未来,共享生态”——支付宝小程序云产品发布会,报名开启!

TRaaS

小程序

企业如何数字化转型?企业数字化转型面临哪些挑战?

优秀

企业数字化转型

精彩纷呈!LLUG 2023 北京场活动回顾来啦 | 视频回放已上线

OpenAnolis小助手

开源 龙蜥社区 LLUG Linux中国 沙龙回顾

MacOS专业截屏指南——截屏技巧和截屏工具分享

互联网工科生

macos mac截图工具

产教融合,华为云GaussDB助推高校国产数据库课程建设

轶天下事

华为云GaussDB入选“2022年数字技术融合创新应用解决方案”

平平无奇爱好科技

车载手势识别技术的挑战和未来发展

来自四九城儿

智能工厂中人工智能应用场景及技术分析

工赋开发者社区

智能工厂 | 上海三菱电梯有限公司电梯智能工厂

工赋开发者社区

瓴羊Quick BI数据门户,让企业数据管理一目了然

巷子

招行&华为云,科技进步一等奖!

轶天下事

车载手势识别技术的原理及其应用

来自四九城儿

无惧百万级并发,GaussDB(for Cassandra)让华为Push推送服务更快触达

轶天下事

【深入浅出Docker原理及实战】「Docker安装说明」零基础+全方位带你学习探索Docker容器开发实战指南(Ubuntu、Debian)

洛神灬殇

Docker 容器 容器化部署

莲子数据与阿里云开源PolarDB合作助力制造业数字化转型

阿里云数据库开源

polarDB PolarDB-X PolarDB-PG

新应用爆发,开启5G的鱼龙之变

脑极体

5G

深耕数据库根技术,华为云云原生数据库推动汽车产业数智升级

轶天下事

解析数字信息时代下的信息安全挑战:大道至简——JNPF快速开发平台的低代码之光

快乐非自愿限量之名

低代码 信息安全 企业数字化 低代码数字转型

停机不收费!华为云RDS for MySQL降本有奇招

轶天下事

一文弄懂 OceanBase 用户管理和访问控制

爱可生开源社区

MySQL oceanbase

亿级用户实时互动,GaussDB(for Redis)带您沉浸式体验迷你世界

轶天下事

为什么MySQL单表不能超过2000万行?

轶天下事

华为云GaussDB践行数字化,护航证券保险高质量发展

平平无奇爱好科技

行业DBA走进华为,共建数据库生态

平平无奇爱好科技

[Paddle] ERNIE-UIE 通用信息抽取模型(含自定义细分领域模型训练)

alexgaoyh

paddle 文心 ERNIE 数据标注 ernie-uie doccano

谷歌正研发“人类最强大模型”!开启智能模型的新纪元:探索迭代发展和低代码开发平台的无限可能

不在线第一只蜗牛

低代码 谷歌 人工智能模型 ChatGPT

华为云GaussDB重磅亮相华为全球智慧金融峰会,产品能力全新升级

轶天下事

华为云GaussDB构建智能数据底座,助推中国数字经济发展

轶天下事

2023-06-27:redis中什么是缓存雪崩?该如何解决?

福大大架构师每日一题

Redis 7 福大大架构师每日一题

【精彩回顾】华为云亮相2023开放原子全球开源峰会

华为云开源

#云原生 #开源

数字化转型下我国分布式数据库应用挑战及发展建议

轶天下事

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