0x00 前言

分析最近的 Crash , 排在前列基本是第三方SDK导致. 遇到这样的问题, 我们需要寻求官方的支持, 但是官方的支持总是来得比较晚. 在Android上, 第三方SDK一般是两种, 一种是jar, 一种是so. so的修改成本过大, 我们放弃它, 我们现在探讨对 jar 的修改.

0x02 问题

在之前 使用AOP来为第三方SDK打CALL 文章 我们使用 AOP 对第三方 SDK 的问题进行修复. 貌似能这样解决这个问题.
但是 AOP 存在局限性:
1、它不够直观. 定义的规则需要通过编译后才能确定.
2、学习成本较高.
3、可操作的范围不够大. 只能切方法.
例子:
在一些因素下,我们升级某个推送SDK版本. 但是发现有大量的NPE异常出现. 通过反编译 jar 定位问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Handler p = null;
private void b() {
if (this.p != null) {
this.p.removeMessages(2);
} else {
this.p = new Handler(Looper.getMainLooper(), new com.xxx.b(this));
}
this.p.sendEmptyMessageDelayed(2, 3000L);
}
private void c() {
if (this.p != null) {
this.p.removeMessages(2);
this.p = null;
}
}
public void d(){
...
// 代码块A 开始
if(p != null){
this.p.sendEmptyMessageDelayed(3, 3000L);
}
// 代码块A 结束
...
}

异常发生在方法this.p.sendEmptyMessageDelayed(2, 3000L);.在一个判断空还会出现NPE, 说明这是一个多线程并发下的bug. 通过分析我们需要

  1. 对b c 方法使用synchronized 修饰
  2. 对代码块A 进行 synchronized(this) 包裹

在这种场景下使用AOP将费力不讨好.

0x03 解决方案

我们可以尝试使用一种更简单的方式来处理这件事情. 输入一个原始的 jar , 经过转换生成新的 jar.

  • 步骤1: 对 jar 中需要修改的 class 反编译成 java 文件.
  • 步骤2: 对 java 文件进行代码逻辑上的 bug fix.
  • 步骤3: 使用 javac 编译 java 文件成 class 文件.
  • 步骤4: 替换 jar 中对应的修改的 class 文件生成新的 jar 文件.

0x04 问题

Q : javac 编译失败.
A: 当你 java 调用的方法不在原有的 jar 中, 导致 javac 编译的时候找不到对应的方法,抛出异常.
解决方式:使用 asm 对 jar 中进行指令分析, 对指令 invokestatic, invokevirtual , invokeinterface , invokedynamic ( android 上可以忽略这个指令) 和 getfield , getstatic 指令的分析. 我们可以生成一个空的实现的jar , 来为 javac 编译提供环境支持.

Q : 内部类的问题.
A: 匿名内部的生成的规则是 外部类类名$Number. Number 是在源码中出现的位置. 当你新增和调整位置的时候会导致生成的类和之前的类不匹配. 解决方式. 在对外部类操作的时候, 直接对匿名内部类进行删除. 等 javac 命令的生成新的内部类直接替换进去.

0x05 改进

通过和 idea 结合提供一整套的解决方案.