大厂Data+Agent 秘籍:腾讯/阿里/字节解析如何提升数据分析智能。 了解详情
写点什么

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

评论

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

Java面试“圣经”,已助朋友拿到7个Offer!2021年金三银四面试知识点合集

Java架构之路

Java 程序员 架构 面试 编程语言

第一个mybatis程序,实现CRUD

xiezhr

mybatis 中间件 crud

你的头发还好吗?大数据分析脱发城市哪里强

不脱发的程序猿

大数据 程序员 程序人生 数据分析 3月日更

软件匠艺

Teobler

敏捷 敏捷开发 软件匠艺 伪敏捷

大作业--联合运营平台

ALone

使用Spark Mllib进行数据分析

大数据技术指南

大数据 spark 28天写作 3月日更

产品训练营--大作业

曦语

产品训练营

白话解读 WebRTC 音频 NetEQ 及优化实践

阿里云CloudImagine

阿里云 音视频 WebRTC 音频技术 视频云

不愧为Java程序员福音 2021阿里巴巴中台架构实战重磅来袭!

比伯

Java 编程 架构 面试 程序人生

史上超强拷贝仓——GitHub 热点速览 v.21.11

HelloGitHub

GitHub 开源

语音通话 2.0

anyRTC开发者

音视频 WebRTC RTC 语音通话

挑灯夜战800个小时,终从外包成功上岸字节!入职那一天我眼眶湿润了「Java岗」

Java架构之路

Java 程序员 架构 面试 编程语言

云通信产品专家翅飞:企业如何提升用户全生命周期管理效率?号码百科来帮忙

阿里云Edge Plus

OpenCV萌新福音:易上手的数字识别实践案例

华为云开发者联盟

OpenCV 图像处理 数字 图像预处理 信用卡

Java面试“圣经”,已助朋友拿到7个Offer!2021年金三银四面试知识点合集

Java架构追梦

Java 阿里巴巴 面试 架构师

最高法两会报告聚焦区块链惩治抄袭,区块链在保护网络著作权方面如何作为?

旺链科技

版权保护 区块链应用

华为在数字化浪潮下的API变革实践

华为云开发者联盟

华为 架构 数字化 API API战略

EFT是什么?EGG公链又是什么?一文带你了解

币圈那点事

公链 挖矿 #区块链#

几个你不知道的dubbo注册中心细节

捉虫大师

zookeeper dubbo 注册中心

【LeetCode】螺旋矩阵 II Java 题解

Albert

算法 LeetCode 28天写作 3月日更

带你全面认识CMMI V2.0(二)

IPD产品研发管理

项目管理 CMMI

还在等机会?Android岗面试12家大厂成功跳槽,最全的BAT大厂面试题整理

欢喜学安卓

android 程序员 面试 移动开发

燃烧吧!开发者们,一起在云端构建开放成熟的 ARM 生态!

亚马逊云科技 (Amazon Web Services)

APP搜索如何又快又准?

华为云开发者联盟

elasticsearch App 搜索 云搜索 词库

别再做智慧园区无效投入了!想要高效运行试试这个方法

一只数据鲸鱼

物联网 数据可视化 智慧城市 智慧园区

我帮大厂做架构之——微信的“N个朋友读过”怎么实现

臧萌

成长 架构师 职场成长

初识Golang之声明变量

Kylin

读书笔记 3月日更 21天挑战 Java转go Go 语言

寻找被遗忘的勇气(十六)

Changing Lin

3月日更

面试必备知识点!2021Android大厂面试知识分享,offer拿到手软

欢喜学安卓

android 程序员 面试 移动开发

吐血整理超全Java进阶教程:基础+容器+并发+虚拟机+IO

Java架构之路

Java 程序员 架构 面试 编程语言

办公自动化:Day01

缭乱地男神

办公自动化 IT蜗壳教学

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