Android Debug 之 Log 最佳实践

在开发过程中,调试是必不可少的一项工作。

当我们要确定项目的逻辑时,当我们要了解界面的生命周期时,当我们发现新写的逻辑与期望效果不一致时,当我们觉得数据有问题时......

而调试有两种方式:

第一种就是使用 debug 模式运行 APP,然后通过断点让程序运行到指定位置进行分析。

第二种就是打日志的方式,通过观察输出来确定程序是否运行到该位置以及此时的数据。

本篇文章主要聚焦在第二种方式上面。

Android 里面,打日志使用的系统 API 是 Log,你以为直接使用就完了吗?

封装

假设你在需要打印日志的地方直接使用系统的 API,那么当遇到下面情况时,会「牵一发而动全身」。

场景一:如果我打印日志要用三方库的日志 API,那么我要查找项目所有使用位置,并一一替换。

场景二:如果我希望在开发环境下打印日志,release 环境不打印,这个时候每个位置都需要单独做处理。

因此我们需要在使用 Log 进行日志打印前,做一层封装。

假设我们的类名字为 ZLog,代码如下:

import android.util.Log; /** * Created on 2019-10-26 * * @author Zengyu.Zhan */ public class ZLog { public static int v(String tag, String msg) { return Log.v(tag, msg); } public static int d(String tag, String msg) { return Log.d(tag, msg); } public static int i(String tag, String msg) { return Log.i(tag, msg); } public static int w(String tag, String msg) { return Log.w(tag, msg); } public static int e(String tag, String msg) { return Log.e(tag, msg); } }

这样处理后,对于场景一和场景二,我们需要修改的只是 ZLog 这个类,而不需要到具体使用 ZLog 的所有地方去修改。

提供日志打印控制

我们知道,日志打印可能包含敏感信息,而且过多的日志打印可能影响 APP 的性能,因此我们一般是在开发时候打开日志,在发布 APP 之前关闭。

因此我们这边需要提供一个标志位来控制日志的打印与否。

import android.util.Log; /** * Created on 2019-10-26 * * @author Zengyu.Zhan */ public class ZLog { private static boolean isDebugMode = false; public static void setDebugMode(boolean debugMode) { isDebugMode = debugMode; } public static int v(String tag, String msg) { return isDebugMode ? Log.v(tag, msg) : -1; } public static int d(String tag, String msg) { return isDebugMode ? Log.d(tag, msg) : -1; } public static int i(String tag, String msg) { return isDebugMode ? Log.i(tag, msg) : -1; } public static int w(String tag, String msg) { return isDebugMode ? Log.w(tag, msg) : -1; } public static int e(String tag, String msg) { return isDebugMode ? Log.e(tag, msg) : -1; } }

默认是不开启日志打印,避免开发者忘记设置。

普通日志和奔溃栈系统日志在控制台的输出对比

现在我们在 APP 里面使用 ZLog 打印日志,代码为:

ZLog.setDebugMode(true); ZLog.e("ZLog", "just test");

输出如下:

Android Debug 之 Log 最佳实践

我们现在增加如下代码:

String nullString = null; if (nullString.equals("null")) { }

运行之后控制台会显示空指针异常奔溃栈,如下:

Android Debug 之 Log 最佳实践

可以看到奔溃栈信息会显示具体是哪个文件出现了空指针,以及具体哪一行。在我们这个例子里面就是 MainActivity.java24 行。

而且点击蓝色链接光标会直接定位到错误位置。

如果我们普通的日志也可以点击就跳转到对应位置,对于我们开发来说效率是有很大提升的。

Android Debug 之 Log 最佳实践

ZLogHelper

既然奔溃栈里面有链接可以跳转,那么我们可以通过栈信息来获取日志的打印位置。

我们直接上代码:

public class ZLogHelper { private static final int CALL_STACK_INDEX = 1; private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$"); public static String wrapMessage(int stackIndex, String message) { // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). if (stackIndex < 0) { stackIndex = CALL_STACK_INDEX; } StackTraceElement[] stackTrace = new Throwable().getStackTrace(); if (stackTrace.length <= stackIndex) { throw new IllegalStateException( "Synthetic stacktrace didn't have enough elements: are you using proguard?"); } String clazz = extractClassName(stackTrace[stackIndex]); int lineNumber = stackTrace[stackIndex].getLineNumber(); message = ".(" + clazz + ".java:" + lineNumber + ") - " + message; return message; } /** * Extract the class name without any anonymous class suffixes (e.g., {@code Foo$1} * becomes {@code Foo}). */ private static String extractClassName(StackTraceElement element) { String tag = element.getClassName(); Matcher m = ANONYMOUS_CLASS.matcher(tag); if (m.find()) { tag = m.replaceAll(""); } return tag.substring(tag.lastIndexOf('.') + 1); } }

这里我们对外提供一个 wrapMessage 方法,看名字就知道是对 Message 进行包装。

方法里面也是对 StackTraceElement 进行分析。

这边还做了一个控制,避免 stackIndex 出现负数情况。

可能有小伙伴会好奇,为什么要把 stackIndex 对外开放呢?

因为你打印日志的地方不一样,这里的 stackIndex 也需要对应调整。

方法里面是对 StackTraceElement 做处理,而 StackTraceElement 跟你的方法层级有关系。

我们以最常用的两种日志打印形式为例,来说明这里的 stackIndex 要怎么传递,以及这个 ZLogHelper 的用法。

直接代码使用

我们在 MainActivity.java 中直接使用,stackIndex 传入 1 即可。

Log.e("ZLog", ZLogHelper.wrapMessage(1, "just test"));

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypjxz.html