卡顿监测之远程收集log(潜入Bugly这趟顺风车)

发布于:2021-12-02 23:57:00

一、问题背景

????接上一篇文章?卡顿监测之真正轻量级的卡顿监测工具BlockDetectUtil(仅一个类)?这篇文章实现了一个轻量级的卡顿监测工具,通过logCat输出log的形式找出卡顿的元凶,可以很方便的在开发中使用,但现在摆在眼前的问题就是当项目上线后,或者遇到无法查看logCat的情况,就不能查看监测的log,尤其是上线后在不同用户的各种各样的手机中,出现卡顿问题几率就更大了,这时候无法查看到log,就无法针对性的排查问题。所以现在就需要接入远程log收集的功能,那么可以让后台写一个提交log数据的接口,然后做个前端展示,但是这些都是需要成本的。所以不妨想想还有什么别的现成的方案,然后我就想到了Bugly。


????那Bugly是什么呢?Bugly是腾讯出品的一个工具集,支持APP应用崩溃日志分析、ANR分析、APP升级,热更新等,由于它傻瓜式的接入,并且信息界面友好、明朗等优点,相信不少开发者都在项目中用到了它。那么针对APP应用崩溃日志分析这一项功能来说,它是可以在应用崩溃时抓取log发送到后台,开发者在后台可以实时地查看到崩溃日志,而且还可以看到相关手机信息,既然它可以在本地抓取异常信息发给后台,那么我们也就肯定可以伪造异常信息(实际是卡顿的堆栈)发送到Bugly后台。有的同学可能要问了,Bugly已经支持采集ANR了,为什么还要卡顿监测,注意了,这里ANR是卡死非卡顿,卡死是卡顿时间达到一定程度所造成的结果。好了,回归正题,既然可以伪造异常,那么当务之急就是得研究Bugly的源码找出它是在哪里提交异常的。


二、研究Bugly源码

? ? 由于代码是混淆的,所以研究起来得有些耐心,首先明确一点的就是它肯定写了一个全局异常捕获的类,这个类实现了Thread.UncaughtExceptionHandler,拿到它的jar包,通过jadx打开,全局搜索实现了这个接口的类,找到了这个类e:

public final class e implements Thread.UncaughtExceptionHandler {
private Context a;
private com.tencent.bugly.crashreport.crash.b b;
private com.tencent.bugly.crashreport.common.strategy.a c;
private com.tencent.bugly.crashreport.common.info.a d;
private Thread.UncaughtExceptionHandler e;
private Thread.UncaughtExceptionHandler f;
private boolean g = false;
private static String h = null;
private static final Object i = new Object();
private int j;

public e(Context var1, b var2, a var3, com.tencent.bugly.crashreport.common.info.a var4) {
this.a = var1;
this.b = var2;
this.c = var3;
this.d = var4;
}
...

public final void uncaughtException(Thread var1, Throwable var2) {
Object var3 = i;
synchronized (i) {
this.a(var1, var2, true, (String) null, (byte[]) null);
}
}
...
}

????那么异常都是通过这个接口的uncaughtException方法回调的,那么我们可以拿到这个类的实例,然后直接伪造一个异常给这个方法吗?显然是不可以的,因为通过这个方法最终会在如下代码里交给系统来处理这个异常,就直接崩溃了



finally {
if(var3) {
if(this.e != null && a(this.e)) {
x.e("sys default last handle start!", new Object[0]);
this.e.uncaughtException(var1, var2);
x.e("sys default last handle end!", new Object[0]);
}

????所以这里我们得找到提交异常到Bugly后台具体方法,经过我多次的调试找到了此方法,那么我是怎么调试的呢,单步调试,执行一个方法就刷新一下Bugly的后台看异常提交上来没有,虽然有点笨,但是很实用,没几下就找到了,就是下面代码里的this.b.a(var11, 3000L, var3),而且也可以看出它是构造了一个CrashDetailBean对象,然后提交这个对象的。



CrashDetailBean var11;
if((var11 = this.b(var1, var2, var3, var4, var5)) != null) {
b.a(var3?"JAVA_CRASH":"JAVA_CATCH", z.a(), this.d.d, var1, z.a(var2), var11);
if(!this.b.a(var11)) {
this.b.a(var11, 3000L, var3);
}

this.b.b(var11);
return;
}

????方法找到了,就是b的a方法,那么我们要调用a方法,就必须得有b实例,而这里b是e的成员变量,所以找到e实例就可以获取b实例,那就先找找e是在那被实例化的。通过全局搜索new e找到具体代码this.r = new e(var2, this.o, this.t, var10),它是在c的构造器里被初始化的。



private c(int var1, Context var2, w var3, boolean var4, com.tencent.bugly.BuglyStrategy.a var5, o var6, String var7) {
a = var1;
var2 = z.a(var2);
this.p = var2;
this.t = a.a();
this.u = var3;
u var8 = u.a();
p var9 = p.a();
this.o = new b(var1, var2, var8, var9, this.t, var5, var6);
com.tencent.bugly.crashreport.common.info.a var10 = com.tencent.bugly.crashreport.common.info.a.a(var2);
this.r = new e(var2, this.o, this.t, var10);
this.s = NativeCrashHandler.getInstance(var2, var10, this.o, this.t, var3, var4, var7);
var10.D = this.s;
this.v = new com.tencent.bugly.crashreport.crash.anr.b(var2, this.t, var10, var3, this.o);
}

? ? 要想获得e实例,既是c里的成员变量r,那么只要获得c实例即可,接下来继续找c是在哪里被实例化的,经过一番查找,找到了它的实例化代码



public static synchronized void a(int var0, Context var1, boolean var2, com.tencent.bugly.BuglyStrategy.a var3, o var4, String var5) {
if(q == null) {
q = new c(1004, var1, w.a(), var2, var3, (o)null, (String)null);
}

}

????看到这个静态方法就鸡冻了有木有,因为显然这个c的实例q是个静态的对象了,那么就好办了



private static c q;? ? 果不其然,那么接下来就可以编写代码了。


三、编写代码

? ? 代码很好写,无非就是反射,按照上面的思路,简单的测试代码就出来了(需要导入Bugly的包,最好就是你的项目里已经用着Bugly了,当然以下代码得放在Bugly初始化的后面)



try {
Object c = ReflectUtil.getStaticField("com.tencent.bugly.crashreport.crash.c","q");
e e = ReflectUtil.getField(c,"r");
b b = ReflectUtil.getField(e,"b");
CrashDetailBean crashDetailBean = (CrashDetailBean) ReflectUtil.invokeMethod(e, "b",
new Class[]{Thread.class,Throwable.class,boolean.class,String.class,byte[].class},
new Object[]{Thread.currentThread(),new Throwable("卡顿监测"),true,null,null});
b.a(crashDetailBean, 3000L, true);
} catch (Exception e) {
e.printStackTrace();
}? ? 经测试,可行,测试结果就不贴了,等与卡顿监测的代码结合后再贴最终的测试结果。


四、与卡顿监测的代码结合

合体!




合体后的超级赛亚人如下



public class BlockDetectUtil {

private static final int TIME_BLOCK = 600;//阈值
private static final int FREQUENCY = 6;//采样频率
private static Handler mIoHandler;
public static void start() {
HandlerThread mLogThread = new HandlerThread("yph");
mLogThread.start();
mIoHandler = new Handler(mLogThread.getLooper());
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK/FREQUENCY);
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mIoHandler.removeCallbacks(mLogRunnable);
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK/FREQUENCY);
Choreographer.getInstance().postFrameCallback(this);
}
});
}
private static Runnable mLogRunnable = new Runnable() {

int time = FREQUENCY;
List list = new ArrayList();
HashMap hashMap = new HashMap();
@Override
public void run() {
if(Debug.isDebuggerConnected())return;
StringBuilder sb = new StringBuilder();
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement s : stackTrace) {
sb.append(s.toString() + "
");
}
list.add(sb.toString());
hashMap.put(sb.toString(),stackTrace);
time -- ;
if(time == 0) {
time = FREQUENCY;
reList(list);
for(String s : list) {
Log.e("BlockDetectUtil", s);
toBugly(hashMap.get(s));
}
list.clear();
hashMap.clear();
}else
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK/FREQUENCY);
}
};
private static void reList(List list){
List reList = new ArrayList<>();
String lastLog = "";
for(String s : list){
if(s.equals(lastLog) && !reList.contains(s)) {
reList.add(s);
}
lastLog = s;
}
list.clear();
list.addAll(reList);
}
private static void toBugly(StackTraceElement[] stacks){
Throwable throwable = new Throwable("卡顿监测");
throwable.setStackTrace(stacks);
try {
Object c = ReflectUtil.getStaticField("com.tencent.bugly.crashreport.crash.c","q");
e e = ReflectUtil.getField(c,"r");
b b = ReflectUtil.getField(e,"b");
CrashDetailBean crashDetailBean = (CrashDetailBean) ReflectUtil.invokeMethod(e, "b",
new Class[]{Thread.class,Throwable.class,boolean.class,String.class,byte[].class},
new Object[]{Looper.getMainLooper().getThread(),throwable,true,null,null});
b.a(crashDetailBean, 3000L, true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
????接下来就是验证了
四、验证

验证代码:






验证结果


?可见,卡顿的堆栈数据已经成功地提交到了Bugly的后台,当然不仅仅堆栈数据,还有其他相关的机型信息,这些都可以帮助我们更好地排查问题。这里可以延伸一下的就是,我们不仅可以利用Bugly这趟顺风车来远程收集应用卡顿的堆栈log,还可以传递其他的数据,这里就需要发挥各位老司机的想象力,看怎么来好好利用这趟免费的顺风车了。
五、总结

? ? 这篇文章主要讲解了如何利用现有的log采集工具Bugly来远程收集应用的卡顿信息,以及展示了超级赛亚人合体之强大。最后,相关源码请前往github处查阅,喜欢的点个?★ 哦 !您的支持,是我荆棘道路上前行的动力。


https://github.com/qq542391099/BlockCollect

相关推荐

最新更新

猜你喜欢