Lancet
Lancet 引入
什么是 Lancet?
Lancet 是一个轻量级 Android AOP 框架,基于 ASM
- 编译速度快, 并且支持增量编译.
- 简洁的 API, 几行 Java 代码完成注入需求.
- 没有任何多余代码插入 apk.
- 支持用于 SDK, 可以在 SDK 编写注入代码来修改依赖 SDK 的 App.
lancet 引入
- 目前最新官方版本 1.0.6 的很久不维护了,不支持 AMS6,在高版本的 AGP 上会报错,一般自己要用得自己修改支持
- 支持 AGP7.x,Transform 废弃
lancet 升级ASM9.1 , 适配Gradle7.5.1 AGP7.3.0 ,调整支持Java11
- AGP8.x AGP 的 Transform API 移除,用不了了
Gradle
1
2
3
4
5
6
7
8
9
10
11
12
// top gradle.build.kts
buildscript {
dependencies {
//noinspection UseTomlInstead
classpath("me.ele:lancet-plugin:2.0.0")
}
}
// app gradle.build.kts
plugins {
id("me.ele.lancet")
}
和 AspectJ 对比
Lancet 本质上是 Gradle Plugin,通过依赖 Android 的打包插件提供的 Transform API,在打包过程中获取到所有的代码。
依赖 ASM 提供的字节码注入能力,通过我们解析自定义的注解,在目标点注入相应的代码。
通过注解进行 AOP 这点和 AspectJ 很相似,但是更加轻量和简洁,使用方式也有所不同。
这里要区分一个概念,编译期注入和运行期注入。
编译期:即在编译时对字节码做插桩修改,达到 AOP 的目的。优点是运行时无额外的性能损耗,但因为编译时的限制,只能修改最终打包到 APK 中的代码,即 Android Framework 的代码是固化在 ROM 中的,无法修改。
运行期:是只在运行时动态的修改代码的执行,因而可以修改 Framework 中代码的执行流程,在 hook 点上执行性能上有所损耗。
- Lancet,编译期注入
- AspectJ,既支持编译期也支持运行期的注入,运行期的注入一般是依赖 JVM 提供的 AttachAPI,因为
Android 没有 JVM 的环境,实际上 class 还会继续转换成 dex,因此 AspectJ 在 Android 平台是只能做到编译期注入。
- Dexposed,运行期注入
使用
- Transform 生成的 class 文件放在
build/intermediates/transforms/lancet/debug
- 可以在 module 或者 library 中使用,在 module 中定义的注解,在 APK 打包时才会被解析,因此在写
module 时不要 apply plugin: me.ele.lancet
代码织入方式
@Proxy
@Proxy 介绍
1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Proxy {
String value();
}
将使用新的方法替换代码里存在的原有的目标方法
应用场景:通常用于对系统 API 的劫持。因为虽然我们不能注入代码到系统提供的库之中,但我们可以劫持掉所有调用系统 API 的地方。
@Proxy 示例
示例:在 Log.d 输出,增加当前调用者的线程信息
1
2
3
4
5
6
7
@Proxy("d")
@TargetClass("android.util.Log")
public static int anyName(String tag, String msg) {
tag = "hacket123." + tag;
msg = msg + "-->>" + Thread.currentThread().getName();
return (int) Origin.call();
}
在 Activity 的 onCreate 调用 Log.d,
1
Log.d("hacket","onCreate。。。。。。")
输出:
onCreate。。。。。。–»main
反编译后:在原有的 msg 后增加了线程信息,原有方法调用替换成 包.aop注解的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class MainActivity extends ComponentActivity {
public static final int $stable = LiveLiterals$MainActivityKt.INSTANCE.m5476Int$classMainActivity();
/* loaded from: classes4.dex */
class _lancet {
private _lancet() {
}
@Proxy("d")
@TargetClass("android.util.Log")
static int com_example_lancetdemos_lancet_LancetAop_anyName(String str, String str2) {
SystemClock.elapsedRealtime();
return Log.d("hacket123." + str, str2 + "-->>" + Thread.currentThread().getName());
}
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_lancet.com_example_lancetdemos_lancet_LancetAop_anyName(LiveLiterals$MainActivityKt.INSTANCE.m5477String$arg0$calld$funonCreate$classMainActivity(), LiveLiterals$MainActivityKt.INSTANCE.m5478String$arg1$calld$funonCreate$classMainActivity());
androidx.activity.compose.ComponentActivity.setContent$default(this, null, ComposableSingletons$MainActivityKt.INSTANCE.m5473getLambda3$app_debug(), 1, null);
}
}
@NameRegex
@NameRegex
用来限制范围操作的作用域. 仅用于 Proxy 模式中, 比如你只想代理掉某一个包名下所有的目标操作. 或者你在代理所有的网络请求时,不想代理掉自己发起的请求. 使用 NameRegex 对 @TargetClass
, @ImplementedInterface
筛选出的 class 再进行一次匹配。
@Insert
@Insert 介绍
1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Insert {
String value();
boolean mayCreateSuper() default false;
}
- 将新代码插入到目标方法原有代码前后。
- 常用于操作 App 与 library 的类,并且可以通过
This
操作目标类的私有属性与方法 - @Insert 当目标方法不存在时,还可以使用
mayCreateSuper
参数来创建目标方法。
@Insert 介绍
示例:注入每一个 Activity 的 onStop 生命周期
1
2
3
4
5
6
7
@TargetClass(value = "android.support.v7.app.AppCompatActivity", scope = Scope.LEAF)
@Insert(value = "onStop", mayCreateSuper = true)
protected void onStop(){
System.out.println("hello world");
Origin.callVoid();
}
- scope 目标是 AppCompatActivity 的所有最终子类
- 如果一个类 MyActivity extends AppcompatActivity 没有重写 onStop 会自动创建 onStop 方法,而 Origin 在这里就代表了 super.onStop(),最终注入效果:
1
2
3
4
protected void onStop() {
System.out.println("hello world");
super.onStop();
}
- 注入后的方法的修饰符(public/protected/private)会完全照搬注解修饰的 Hook 方法的修饰符
匹配目标类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public @interface TargetClass {
String value();
Scope scope() default Scope.SELF;
}
public @interface ImplementedInterface {
String[] value();
Scope scope() default Scope.SELF;
}
public enum Scope {
SELF,
DIRECT,
ALL,
LEAF
}
- TargetClass 类
- ImplementedInterface 接口
@TargetClass 通过类
- value 是一个类的全名
- scope
- Scope.SELF 代表仅匹配 value 指定的目标类本身
- Scope.DIRECT 代表匹配 value 指定类的直接子类
- Scope.All 代表匹配 value 指定类的所有子类
- Scope.LEAF 代表匹配 value 指定类的最终子类。Java 是单继承,所以继承关系是树形结构,所以这里代表了指定类为顶点的继承树的所有叶子节点.
@ImplementedInterface
- value 可以填写多个接口的全名
- scope
- Scope.SELF : 代表直接实现所有指定接口的类
- Scope.DIRECT : 代表直接实现所有指定接口,以及指定接口的子接口的类
- Scope.ALL: 代表 Scope.DIRECT 指定的所有类及他们的所有子类
- Scope.LEAF: 代表 Scope.ALL 指定的森林结构中的所有叶节点
示例:当我们使用@ImplementedInterface(value = “I”, scope = …) 时, 目标类如下:
- Scope.SELF -> A
- Scope.DIRECT -> A C
- Scope.ALL -> A B C D
- Scope.LEAF -> B D
如何获取类的全名
- 右键类,Copy→Copy Reference
- this.getClass() 看日志输出
- javap 命令
- 进入 app/build/intermediates/javac/debug/classes/com/example/lancetdemos
- javap -c MyRunnable
- 三方库,用 jadx-gui,上面会注释内部类的全路径名
匿名内部类 com.appsflyer.internal.AFd1pSDK.4�是不对的,正确的应该是 com.appsflyer.internal.AFd1pSDK$4
- ClassyShark
java -jar $HACK_HOME/ClassyShark.jar -open
匹配目标方法
虽然在 Proxy , Insert 中我们指定了方法名, 但识别方法必须要更细致的信息. 我们会直接使用 Hook 方法的修饰符,参数类型来匹配方法.
所以一定要保持 Hook 方法的 public/protected/private static 信息与目标方法一致,参数类型,返回类型与目标方法一致.
返回类型可以用 Object 代替.
方法名不限. 异常声明也不限.
@ClassOf
没有权限声明目标类,用@ClassOf 注解来替代对类的直接 import。
ClassOf 的 value 一定要按照 (package_name.)(outer_class_name$)inner_class_name([]…)
的模板。
比如:
- java.lang.Object
- java.lang.Integer[][]
- A[]
- A$B
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class A {
protected int execute(B b){
return b.call();
}
private class B {
int call() {
return 0;
}
}
}
@TargetClass("com.dieyidezui.demo.A")
@Insert("execute")
public int hookExecute(@ClassOf("com.dieyidezui.demo.A$B") Object o) {
System.out.println(o);
return (int) Origin.call();
}
API
Origin
Origin 用来调用原目标方法,可以被多次调用。
- Origin.call() 用来调用有返回值的方法
- Origin.callVoid() 用来调用没有返回值的方法
如果你有捕捉异常的需求,可以使用:
- Origin.call/callThrowOne/callThrowTwo/callThrowThree()
- Origin.callVoid/callVoidThrowOne/callVoidThrowTwo/callVoidThrowThree()
This
仅用于 @Insert
方式的非静态方法的 Hook 中,否则报错
- get() 返回目标方法被调用的实例化对象
- putField & getField
你可以直接存取目标类的所有属性,无论是 protected or private.
另外,如果这个属性不存在,我们还会自动创建这个属性. Exciting!
自动装箱拆箱肯定也支持了.
注意:
- Proxy 不能使用 This
- 你不能存取你父类的属性. 当你尝试存取父类属性时,我们还是会创建新的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package me.ele;
public class Main {
private int a = 1;
public void nothing(){
}
public int getA(){
return a;
}
}
@TargetClass("me.ele.Main")
@Insert("nothing")
public void testThis() {
Log.e("debug", This.get().getClass().getName());
This.putField(3, "a");
Origin.callVoid();
}
示例
匿名内部类
1
2
3
4
5
6
7
8
9
public class StaticClassTest {
public static class InnerClass {
public int i;
public void test() {
System.out.println("InnerClass test");
}
}
}
hook:
1
2
3
4
5
6
7
8
@Insert("test")
@TargetClass(value = "com.example.lancetdemos.StaticClassTest$InnerClass", scope = Scope.SELF)
public void test() {
StaticClassTest.InnerClass innerClass = (StaticClassTest.InnerClass) This.get();
innerClass.i = 100;
System.out.println("StaticClassTest.InnerClass test aop i=" + innerClass.i);
Origin.callVoid();
}
捕获 MyRunnable 崩溃
原始代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyRunnable implements Runnable {
@Override
public void run() {
Log.w("hacket", "MyRunnable run...");
try {
Thread.sleep(3000);
Log.d("hacket", "MyRunnable run sleep 3 end...");
int i = 1 / 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
hook:
1
2
3
4
5
6
7
8
9
10
@TargetClass(value = "com.example.lancetdemos.MyRunnable", scope = Scope.SELF)
@Insert(value = "run", mayCreateSuper = false)
protected void run() {
Log.d("hacket", "MyRunnable aop run.");
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
- 原有的 run 方法替换成了
_lancet.com_example_lancetdemos_lancet_LancetAop_run(this);
,这里我们进行了 try catch
方法内的方法捕获
原代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyRunnable implements Runnable {
@Override
public void run() {
Log.w("hacket", "MyRunnable run...");
try {
Thread.sleep(3000);
Log.d("hacket", "MyRunnable run sleep 3 end...");
// int i = 1 / 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
class InnerRunnable implements Runnable {
@Override
public void run() {
Log.i("hacket", "InnerRunnable run start. class=" + this.getClass());
String s = null;
s.toLowerCase();
Log.d("hacket", "InnerRunnable run end...");
}
}
InnerRunnable innerRunnable = new InnerRunnable();
innerRunnable.run();
}
}
hook 代码:
1
2
3
4
5
6
7
8
9
10
@TargetClass(value = "com.example.lancetdemos.MyRunnable$1InnerRunnable", scope = Scope.SELF)
@Insert(value = "run", mayCreateSuper = false)
protected void run() {
Log.d("hacket", "MyRunnable$InnerRunnable aop run add try catch.");
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
callThrowOne 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 原始
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
}
@TargetClass("java.io.InputStream")
@Proxy("read")
public int read(byte[] bytes) throws IOException {
try {
return (int) Origin.<IOException>callThrowOne();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
修复三方 sdk 的 crash
appsflyer 崩溃
1
2
3
4
5
6
7
8
9
10
@TargetClass(value = "com.appsflyer.internal.AFd1pSDK$4", scope = Scope.SELF)
@Insert(value = "run", mayCreateSuper = false)
protected void run1() {
Log.d("hacket", "com.appsflyer.internal.AFd1pSDK.4 aop run add try catch.");
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
插桩 OkHttp 添加 Flipper 的 Interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OkHttpHook {
// hook okhttp,添加一个拦截器
@Insert("build")
@TargetClass("okhttp3.OkHttpClient$Builder")
public OkHttpClient hookBuild() {
System.out.println("hook okhttp");
OkHttpClient.Builder builder = (OkHttpClient.Builder) This.get();
builder.addNetworkInterceptor(FlipperTool.getFlipperOkhttpInterceptor());
OkHttpClient client = (OkHttpClient) Origin.call();
return client;
}
}
修复案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
public class CrashHook {
//捕捉ConnectionTracker中unbindService出现异常的情况
@Insert("unbindService")
@TargetClass("com.google.android.gms.common.stats.ConnectionTracker")
public void changeUnbindService(Context var1, ServiceConnection var2) { //getGoogleAnalytics
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
//捕捉RecyclerView动画崩溃问题,
@TargetClass("androidx.recyclerview.widget.SimpleItemAnimator")
@Insert("dispatchChangeFinished")
public void changeUnbindService(RecyclerView.ViewHolder item, boolean oldItem) { //getGoogleAnalytics
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
// @TargetClass("com.alibaba.android.arouter.launcher._ARouter")
// @Insert("startActivity") //可能是options出错了,去掉options重试
// private void startRouterActivity(int requestCode,
// Context currentContext,
// Intent intent,
// Postcard postcard,
// NavigationCallback callback) {
// if (requestCode >= 0) { // Need start for result
// if (currentContext instanceof Activity) {
// try {
// ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
// } catch (Throwable e) {
// ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, null);
// FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
// }
// } else {
// Log.w(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
// }
// } else {
// try {
// ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
// } catch (Throwable e) {
// ActivityCompat.startActivity(currentContext, intent, null);
// FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
// }
// }
// if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
// try {
// ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
// } catch (Throwable e) {
// e.printStackTrace();
// FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
// }
// }
// if (null != callback) { // Navigation over.
// callback.onArrival(postcard);
// }
// }
@TargetClass("okio.RealBufferedSink")
@Insert("flush") //可能是options出错了,去掉options重试
public void okioFlush() throws IOException {
try {
Origin.callVoid();
} catch (Throwable e) {
if (e instanceof IOException) {
throw e;
} else {
throw new IOException(e);
}
}
}
//线上Google play日志,数据库打开有少量报错,firebase日志记录分析原因
// @Insert("getWritableDatabase")
// @TargetClass("androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper")
// public SupportSQLiteDatabase getWritableDatabase() {
// try {
// return (SupportSQLiteDatabase) Origin.call();
// } catch (Throwable e) {
// try {
// FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
// } catch (Throwable ignore) {
// }
// throw e;
// }
// }
@TargetClass("com.facebook.animated.giflite.draw.MovieFrame")
public void renderGifFrame(int w, int h, Bitmap bitmap) {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
@Insert("setAlarm")
@TargetClass("androidx.work.impl.utils.ForceStopRunnable")
static void setAlarmFix(Context context) {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
@Insert("updateScrollEventValues")
@TargetClass("androidx.viewpager2.widget.ScrollEventAdapter")
private void fixUpdateScrollEvent() {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
@Insert("closeQuietly")
@TargetClass("okhttp3.internal.Util")
public static void fixCloseQuietly(Socket socket) {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
/* @Insert("onActivityCreated")
@TargetClass("com.google.firebase.messaging.FcmLifecycleCallbacks")
public void fixFcmOnCreate(Activity var1, Bundle var2) {
try {
Origin.callVoid();
} catch (Exception e) {
try {
FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
} catch (Exception ignore) {
}
e.printStackTrace();
}
}*/
//Fatal Exception: java.util.NoSuchElementException
// @TargetClass("okhttp3.internal.connection.ConnectInterceptor")
// public okhttp3.Response fixFindConnection(Interceptor.Chain chain) throws IOException {
// try {
// return (okhttp3.Response) Origin.call();
// } catch (Throwable e) {
// try {
// FirebaseCrashlyticsProxy.INSTANCE.recordException(e);
// } catch (Throwable ignore) {
// }
// if (!(e instanceof IOException)) {
// throw new IOException(e);
// } else {
// throw e;
// }
// }
// }
@Insert("tryIntent")
@TargetClass("com.facebook.login.NativeAppLoginMethodHandler")
protected boolean fixTryIntent(Intent intent, int requestCode) {
try {
return (Boolean) Origin.call();
} catch (Throwable e) {
e.printStackTrace();
return false;
}
}
@Insert("prefetchPositionWithDeadline")
@TargetClass("androidx.recyclerview.widget.GapWorker")
private RecyclerView.ViewHolder fixRecyclerViewDeadline(RecyclerView view, int position, long deadlineNs) {
try {
return (RecyclerView.ViewHolder) Origin.call();
} catch (Throwable e) {
e.printStackTrace();
return null; //返回null,reclerView某些情况也会出错崩溃
}
}
//临时修复
@Insert("dispatchStop")
@TargetClass("androidx.fragment.app.FragmentController")
public void fixFragmentDispatchStop() {
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
@Insert("isForceStopped")
@TargetClass("androidx.work.impl.utils.ForceStopRunnable")
public boolean fixForceStopped() {
try {
return (Boolean) Origin.call();
} catch (Throwable e) {
return false;
}
}
@TargetClass(value = "com.facebook.login.LoginFragment")
@Insert(value = "onActivityResult")
public void fixFacebookLoginResult(int requestCode, int resultCode, Intent data) {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
@TargetClass(value = "com.google.android.material.resources.TypefaceUtils")
@Insert(value = "maybeCopyWithFontWeightAdjustment")
public static Typeface fixTypefaceCrash(android.content.res.Configuration configuration, Typeface typeface) {
try {
return (Typeface) Origin.call();
} catch (Throwable e) {
return null;
}
}
// @TargetClass("com.huawei.hms.push.HmsMessageService")
// @Insert("handleIntentMessage")
// private void dispatchMessageHuaWei(Intent intent) {
// Log.w("aws_push", "aws_push HmsMessageService dispatchMessage1");
// Origin.callVoid();
// NotifyReport.dispatchMessageT(intent);
// Log.w("aws_push", "aws_push HmsMessageService dispatchMessage2");
// }
@TargetClass("com.tencent.mmkv.MMKV")
@Insert("getAll")
public Map<String, ?> catchMMKVGetAll() {
try {
return (Map<String, ?>) Origin.call();
} catch (Throwable e) {
e.printStackTrace();
return new HashMap<>();
}
}
@TargetClass("com.tencent.mmkv.MMKV")
@Insert("registerOnSharedPreferenceChangeListener")
public void catchMMKVRegisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
try {
Origin.callVoid();
} catch (Throwable e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
@TargetClass("com.tencent.mmkv.MMKV")
@Insert("unregisterOnSharedPreferenceChangeListener")
public void catchMMKVUnregisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
try {
Origin.callVoid();
} catch (Throwable e) {
e.printStackTrace();
}
}
//启动页res/drawable/abc_vector_test.xml 图片加载失败,导致启动页崩溃
@Insert("getDrawableIfKnown")
@TargetClass("androidx.appcompat.widget.TintTypedArray")
public Drawable fixVectorCrash(int index) {
try {
return (Drawable) Origin.call();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//商详页大图模块FragmentStateAdapter onStateChanged导致的崩溃
@Insert("consumeRestoredStateForKey")
@TargetClass("androidx.savedstate.SavedStateRegistry")
public Bundle consumeRestoredStateForKey(String key) {
if (BuildConfig.DEBUG) {
return (Bundle) Origin.call();
} else {
try {
return (Bundle) Origin.call();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
//商详/拍照等页面大图片会Crash
@Insert("draw")
@TargetClass("com.facebook.drawee.drawable.ForwardingDrawable")
public void draw(Canvas canvas) {
boolean check = true;
try {
Object o = This.get();
check = ForwardingDrawableCheckUtils.check(o);
} catch (Exception e) {
e.printStackTrace();
}
if (check) {
Origin.callVoid();
}
}
//购物车刷新推荐列表 mChildHelper为空导致firebase少量崩溃
@Insert("removeView")
@TargetClass("androidx.recyclerview.widget.RecyclerView$LayoutManager")
public void removeView(View child) {
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
// 列表滑动回收的时候,抛异常,捕获崩溃
@Insert("recycleViewHolderInternal")
@TargetClass("androidx.recyclerview.widget.RecyclerView$Recycler")
public void recycleViewHolderInternal(RecyclerView.ViewHolder holder) {
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
*
* fix appsflyer sdk 6.10.3
*/
@TargetClass(value = "com.appsflyer.internal.AFd1pSDK$4", scope = Scope.SELF)
@Insert(value = "run")
public void run() {
try {
Origin.callVoid();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意坑
Module requires ASM6
版本太旧
官方的版本太旧没维护了,不支持 7.x+ 高版本的;AGP8.0 不支持
需要自己 fork 维护,特别是 AGP8.0 后,Transformer API 移除了
不支持类的构造方法 hook
1
2
3
4
5
6
@Proxy("<init>")
@TargetClass("com.example.lancetdemos.MyOkhttpClient")
void OkHttpClient(MyOkhttpClient.Builder builder) {
builder.i = 10024;
Origin.callVoid();
}