剑客
关注科技互联网

Android StackTraceElement与Log工具类设计

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): helloworld01
10-25 23:00:24.063: D/test:MainActivity.test01(L:17): helloworld02

参考资料

JDK Logging模块深入分析

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

StackTraceElement类使用说明

xUtils LogUtil类设计

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址