Android 从StackTraceElement反观Log库,GitHub标星8k

二、可行性


对于好用,我觉得如下用法就可以:

L.e(“heiheihei”);

对于好定位,当然是可以通过日志信息点击,定位到具体行,所以今天demo代码的效果是这样的:

当然了,你可以根据自己喜好,去添加各种信息,以及装饰。

那么,现在最大的一个问题就是

  • 我怎么输出具体的日志调用行呢?

这个秘密就在:

Thread.currentThread().getStackTrace();

我们可以通过当前的线程,拿到当前调用的栈帧集合(称呼不一定准备)。

  • 这个栈帧集合是什么玩意呢?

你可以理解为当我们调用方法的时候,每进入一个方法,会将该方法的相关信息(例如:类名,方法名,方法调用行数等)存储下来,压入到一个栈中,当方法返回的时候再将其出栈。

下面看个具体的例子:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

a();

}

void a() {

b();

}

void b() {

StringBuffer err = new StringBuffer();

StackTraceElement[] stack = Thread.currentThread().getStackTrace();

for (int i = 0; i < stack.length; i++) {

err.append("\tat ");

err.append(stack[i].toString());

err.append(“\n”);

}

Log.e(“TAG”, err.toString());

}

我在onCreate中,调用了a方法,然后a中调用的b方法。在b方法中打印出当前线程中的栈帧集合信息。

at dalvik.system.VMStack.getThreadStackTrace(Native Method)

at java.lang.Thread.getStackTrace(Thread.java:579)

at com.zxy.recovery.test.MainActivity.b(MainActivity.java:26)

at com.zxy.recovery.test.MainActivity.a(MainActivity.java:21)

at com.zxy.recovery.test.MainActivity.onCreate(MainActivity.java:17)

at android.app.Activity.performCreate(Activity.java:5231)

可以看到我们整个方法的调用过程,底部的最先开始调用,顺序为onCreate->a->b->Thread.getStackTrace->VMStack.getThreadStackTrace.

最后两个是因为我们的stacks是在VMStack.getThreadStackTrace方法中获取,然后返回的,所以包含了这两个的内部调用信息。

这里我们直接调用的StackTraceElement的toString方法,它内部有:

  • getClassName

  • getMethodName

  • getFileName

  • getLineNumber

看名字就知道什么意思了,我们可以根据这些信息拼接要打印的信息。

所以,不管怎么说,我们现在已经确定了,可以通过该种方式得到我们的调用某个方法的行数,而且是支持点击跳转到指定位置的。

到这里相当于,方案的可行性就通过了,剩下就是码代码了。

三、实现


先写个大致的代码:

public class L{

private static boolean sDebug = true;

private static String sTag = “zhy”;

public static void init(boolean debug, String tag){

L.sDebug = debug;

L.sTag = tag;

}

public static void e(String msg, Object… params){

e(null, msg, params);

}

public static void e(String tag, String msg, Object[] params){

if (!sDebug) return;

tag = getFinalTag(tag);

//TODO 通过stackElement打印具体log执行的行数

Log.e(tag, content);

}

private static String getFinalTag(String tag){

if (!TextUtils.isEmpty(tag)){

return tag;

}

return sTag;

}

}

因为我平时基本上只用Log.e,所以我就不对其他方法进行处理了,你可以根据你的喜好来决定。

ok,那么现在只有一个地方没有处理,就是打印log执行的类以及代码行。

我在onCreate的17行调用了:

L.e(“Hello World”);

然后在e()方法中,打印了所有的栈帧信息:

E/zhy: at dalvik.system.VMStack.getThreadStackTrace(Native Method)

at java.lang.Thread.getStackTrace(Thread.java:579)

at com.zxy.recovery.test.L.e(L.java:32)

at com.zxy.recovery.test.L.e(L.java:25)

at com.zxy.recovery.test.MainActivity.onCreate(MainActivity.java:19)

at android.app.Activity.performCreate(Activity.java:5231)

//…

E/zhy: Hello World

我们要输出的就是上述的MainActivity.onCreate(MainActivity.java:19)

  • 那么我们如何定位呢?

观察上面的信息,因为我们的入口是L类的方法,所以,我们直接遍历,L类相关的下一个非L类的栈帧信息就是具体调用的方法。

于是我们这么写:

private StackTraceElement getTargetStackTraceElement() {

// find the target invoked method

StackTraceElement targetStackTrace = null;

boolean shouldTrace = false;

StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

for (StackTraceElement stackTraceElement : stackTrace) {

boolean isLogMethod = stackTraceElement.getClassName().equals(L.class.getName());

if (shouldTrace && !isLogMethod) {

targetStackTrace = stackTraceElement;

break;

}

shouldTrace = isLogMethod;

}

return targetStackTrace;

}

拿到确定的方法调用相关的栈帧之后,就是输出啦~~

添加到e()方法中:

public static void e(String tag, String msg, Object… params) {

if (!sDebug) return;

String finalTag = getFinalTag(tag);

StackTraceElement targetStackTraceElement = getTargetStackTraceElement();

Log.e(finalTag, “(” + targetStackTraceElement.getFileName() + “:”

  • targetStackTraceElement.getLineNumber() + “)”);

Log.e(finalTag, String.format(msg, params));

}

现在再看下输出结果:

现在就可以迅速的定位到日志输出行,再也不要全局搜索去查找了~

到这里,对于我个人的需求已经满足了,如果你有特殊需要,比如也想像logger那样搞个框,那就自己绘制吧,也可以参考它的源码。

对了,还有json,有时候希望可以看json字符串更加的直观,像looger那样:

你可以参考它的做法,其实就是将json字符串,通过JsonArray和JsonObject进行了一个类似format这样的操作。

private static String getPrettyJson(String jsonStr) {

try {

jsonStr = jsonStr.trim();

if (jsonStr.startsWith(“{”)) {

JSONObject jsonObject = new JSONObject(jsonStr);

return jsonObject.toString(JSON_INDENT);

}

if (jsonStr.startsWith(“[”)) {

JSONArray jsonArray = new JSONArray(jsonStr);

return jsonArray.toString(JSON_INDENT);

}

} catch (JSONException e) {

e.printStackTrace();

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。

当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。

[外链图片转存中…(img-oQGz7J7W-1712347313624)]

当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值