Android StackTraceElement与Log工具类设计

StackTraceElement引入 StackTraceElement这个类在开发中并不常见,但是做过Java2EE开发的人员肯定不陌生 java util logging Logg

StackTraceElement引入

StackTraceElement这个类在开发中并不常见,但是做过Java2EE开发的人员肯定不陌生 java.util.logging.Logger类或log4j工具包,当我们使用日志功能的时候,系统为我们打印很丰富的信息,格式如下:

运行时间] [当前类名] [方法名]

INFO: [用户信息]

java.util.logging.Logger打印log信息

十月 24, 2016 9:06:33 下午 com.demo.MainTestmain警告: helloworldwarning十月 24, 2016 9:06:33 下午 com.demo.MainTestmain信息: helloworldinfo

使用log4j的tomcat日志

十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog信息: Serverversion:        ApacheTomcat/7.0.70十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog信息: Serverbuilt:          Jun 15 2016 16:27:45 UTC十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog信息: Servernumber:        7.0.70.0十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog信息: OSName:              Windows 7

相比较而言Android的日志输出就有些简单粗暴了,虽然时间日期可有LogCat自动输出,可是方法名或类名都没有,需要我们自己将方法名或者类名手动输入tag标签或者msg才可以。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

问题来了,为什么 java.util.logging.Logger类或log4j工具包可以输出很规范详细的信息呢?接下来我们查看一下源码设计来追本溯源。

在JDK的源代码里面java.util.logging.LogRecord类的inferCaller方法。

private void inferCaller() { needToInferCaller = false; JavaLangAccessaccess = SharedSecrets.getJavaLangAccess(); Throwablethrowable = new Throwable(); int depth = access.getStackTraceDepth(throwable);  boolean lookingForLogger = true; for (int ix = 0; ix < depth; ix++) { // Calling getStackTraceElement directly prevents the VM // from paying the cost of building the entire stack frame. StackTraceElementframe = access.getStackTraceElement(throwable, ix); String cname = frame.getClassName(); boolean isLoggerImpl = isLoggerImplFrame(cname); if (lookingForLogger) { // Skip all frames until we have found the first logger frame. if (isLoggerImpl) { lookingForLogger = false; } } else { if (!isLoggerImpl) { // skip reflection call if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {  // We've found the relevant frame.  setSourceClassName(cname);  setSourceMethodName(frame.getMethodName());  return; } } } } // We haven't found a suitable frame, so just punt.  This is // OK as we are only committed to making a "best effort" here.}

log4j中LocationInfo类中一个静态代码块设计如下:

static {      try {          Class[] noArgs = null;          getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);          Class stackTraceElementClass = Class.forName("java.lang.StackTraceElement");          getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);          getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);          getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);          getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);      } catch(ClassNotFoundExceptionex) {          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");      } catch(NoSuchMethodExceptionex) {          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");      }  }

两个日志工具类都使用了一个类StackTraceElement,只是获取方式不一样,下面我们介绍一下该类。

StackTraceElement介绍

堆栈跟踪中的元素,它由Throwable.getStackTrace()返回。每个元素表示单独的一个堆栈帧。所有的堆栈帧(堆栈顶部的那个堆栈帧除外)都表示一个方法调用。堆栈顶部的帧表示生成堆栈跟踪的执行点。通常,这是创建对应于堆栈跟踪的 throwable 的点。

StackTraceElement是JDK1.4开始引入的,而java.util.logging.Logger类也是JDK1.4开始引入的,某种意义上来讲StackTraceElement就是为java.util.logging.Logger的实现而设计的。在初始引入是是通过Throwable类通过 getStackTrace()方法获取一个StackTraceElement数组,在JDK1.5中Thread类引入了 getStackTrace()getAllStackTraces()两个方法。这下子,我们不用 (new Throwable()).getStackTrace();可以调用 Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。

Throwable类部分代码:

public StackTraceElement[] getStackTrace() { return getOurStackTrace().clone();}private synchronized StackTraceElement[] getOurStackTrace() { // Initialize stack trace field with information from // backtrace if this is the first call to this method if (stackTrace == UNASSIGNED_STACK || (stackTrace == null && backtrace != null) /* Out of protocol state */) { int depth = getStackTraceDepth(); stackTrace = new StackTraceElement[depth]; for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i); } else if (stackTrace == null) { return UNASSIGNED_STACK; } return stackTrace;} 

Thread类部分代码:

public StackTraceElement[] getStackTrace() { if (this != Thread.currentThread()) { // check for getStackTrace permission SecurityManagersecurity = System.getSecurityManager(); if (security != null) { security.checkPermission( SecurityConstants.GET_STACK_TRACE_PERMISSION); } // optimization so we do not call into the vm for threads that // have not yet started or have terminated if (!isAlive()) { return EMPTY_STACK_TRACE; } StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this}); StackTraceElement[] stackTrace = stackTraceArray[0]; // a thread that was alive during the previous isAlive call may have // since terminated, therefore not having a stacktrace. if (stackTrace == null) { stackTrace = EMPTY_STACK_TRACE; } return stackTrace; } else { // Don't need JVM help for current thread return (new Exception()).getStackTrace(); }}public static Map<Thread, StackTraceElement[]> getAllStackTraces() { // check for getStackTrace permission //... // Get a snapshot of the list of all threads Thread[] threads = getThreads(); StackTraceElement[][] traces = dumpThreads(threads); Map<Thread, StackTraceElement[]> m = new HashMap<>(threads.length); for (int i = 0; i < threads.length; i++) { StackTraceElement[] stackTrace = traces[i]; if (stackTrace != null) { m.put(threads[i], stackTrace); } // else terminated so we don't put it in the map } return m;}

上面已经说了StackTraceElement就是一个堆栈帧,在方法栈中每个方法都有一个StackTraceElement,每个StackTraceElement中保存了方法的相关信息,如方法所处的类、方法名、方法所处类文件名称以及该方法执行行号。

  • String getClassName()返回类的完全限定名,该类包含由该堆栈跟踪元素所表示的执行点。
  • String getFileName()返回源文件名,该文件包含由该堆栈跟踪元素所表示的执行点。
  • int getLineNumber()返回源行的行号,该行包含由该堆栈该跟踪元素所表示的执行点。
  • String getMethodName()返回方法名,此方法包含由该堆栈跟踪元素所表示的执行点。
public static void main(String[] args) { test();}private static void test(){ StackTraceElement[] caller = new Throwable().getStackTrace(); for(int i=0;i<caller.length;i++){ System.out.println("====> "+caller[i].getClassName()); System.out.println("====> "+caller[i].getMethodName()); System.out.println("====> "+caller[i].getFileName()); System.out.println("====> "+caller[i].getLineNumber()); System.out.println("----------------------------------"); }}

上面方法打印结果如下:

====> com.demo.MainTest====> test====> MainTest.java====> 11----------------------------------====> com.demo.MainTest====> main====> MainTest.java====> 7----------------------------------

在MainTest类中首先定义了一个test方法,然后在main方法中调用,由打印信息知道该次执行方法栈深度为2,方法调用的相关信息也如数打印了出来。上述方法是使用Throwable类获取的方法栈信息,如果使用Thread获取会多出来一个层级,示例代码如下:

public static void main(String[] args) { test01(); System.out.println("---------------------------------------"); test02();}private static void test01(){ StackTraceElement[] caller = new Throwable().getStackTrace(); for(int i=0;i<caller.length;i++){ System.out.println("Throwable: "+caller[i].toString()); }}private static void test02(){ StackTraceElement[] caller = Thread.currentThread().getStackTrace(); for(int i=0;i<caller.length;i++){ System.out.println("Thread: "+caller[i].toString()); }}
Throwable: com.demo.MainTest.test01(MainTest.java:39)Throwable: com.demo.MainTest.main(MainTest.java:7)---------------------------------------Thread: java.lang.Thread.getStackTrace(UnknownSource)Thread: com.demo.MainTest.test02(MainTest.java:45)Thread: com.demo.MainTest.main(MainTest.java:9)

Android Log的设计

在Android的原生Log需要两个参数一个是tag标签一个是msg,我们只需要将tag重新设计一下然后作为原生Log相关方法中tag标签,从LogCat中我们开发中最想知道的信息给出来就可以了,如类名、方法名还有方法调用的行数,时间信息我们不需要从新赋值,因为LogCat会自动打印出Log执行时的时间信息。

整个类的设计很简单,核心就在tag的设计上面,LogUtils类的代码如下:

public class LogUtils {  private static final boolean DEBUG = true; public static String tagPrefix = "";  private LogUtils() { }  private static String generateTag() { StackTraceElementcaller = new Throwable().getStackTrace()[2]; String tag = "%s.%s(L:%d)"; String callerClazzName = caller.getClassName(); callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1); tag = String.format(tag, callerClazzName, caller.getMethodName(), caller.getLineNumber()); tag = TextUtils.isEmpty(tagPrefix) ? tag : tagPrefix + ":" + tag; return tag; }  public static void d(String content) { if (!DEBUG) return; String tag = generateTag();  Log.d(tag, content); }  public static void d(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.d(tag, content, tr); }  public static void e(String content) { if (!DEBUG) return; String tag = generateTag();  Log.e(tag, content); }  public static void e(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.e(tag, content, tr); }  public static void i(String content) { if (!DEBUG) return; String tag = generateTag();  Log.i(tag, content); }  public static void i(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.i(tag, content, tr); }  public static void v(String content) { if (!DEBUG) return; String tag = generateTag();  Log.v(tag, content); }  public static void v(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.v(tag, content, tr); }  public static void w(String content) { if (!DEBUG) return; String tag = generateTag();  Log.w(tag, content); }  public static void w(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.w(tag, content, tr); }  public static void w(Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.w(tag, tr); }  public static void wtf(String content) { if (!DEBUG) return; String tag = generateTag();  Log.wtf(tag, content); }  public static void wtf(String content, Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.wtf(tag, content, tr); }  public static void wtf(Throwabletr) { if (!DEBUG) return; String tag = generateTag();  Log.wtf(tag, tr); }}

写一个简单的测试类验证一下,测试类代码如下:

public class MainActivity extends Activity { protected void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LogUtils.tagPrefix="test"; LogUtils.d("hello world01"); test01(); } private void test01(){ LogUtils.d("hello world02"); }}
10-25 23:00:24.063: D/test:MainActivity.onCreate(L:13): helloworld0110-25 23:00:24.063: D/test:MainActivity.test01(L:17): helloworld02

参考资料

JDK Logging模块深入分析

Log信息获取调用类和调用方法名的实现原理

StackTraceElement类使用说明

xUtils LogUtil类设计

未登录用户
全部评论0
到底啦