在开发和应用的开发和调试过程中难免会发现故障的过程中。我相信很多做iOS开发程序员Xcode的debug调试功能大加关注。
但在这样做Android开发过程中,却不那么方便,虽然IDE也提供了debug模式提供给开发人员使用。
就我个人而言eclipse的debug调试较之于Xcode能够说是一个天上。一个地下。
因此。在日常开发中,常使用到的便是android.util包下的Log类进行调试打印输出。当然非常多筒子们仍会继续沿用System.out.println来打印输出,在Android开发中并不推荐此种方式。不仅会代码冗余,并且在程序编译打包时去除Log会十分的繁琐。
以下我们先来看看普通情况下在LogCat输出日志信息所做的操作
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName(); // "MainActivity" @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); android.util.Log.d("test", "我是測试信息");
}
}
此时我们能够看到控制台会输出
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGpsNDYxMjYwOTEx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
上述情况是最主要的输出调试。此时我们知道有非常大局限性。
假如我们的项目非常的庞大,代码量达到几十万行的层级时。
为了不消耗资源。在公布打包版本号的时候须要去除打印输出语句,这是就显得非常乏力。此时可能我们会想出非常多办法比方定义一个布尔类型的debug开关,在须要打包的时候将其关闭。
详细请看实现。以下是我一年前封装的一个Log类
package com.example.debuglog; import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import android.os.Environment;
/**
* @author J!nl!n
* @date 2013-12-30
* @time 下午12:29:18
* @todo 提供扩展Log类
*/
public class Log {
// private static boolean isOpen = isOpenLog();
private static boolean isOpen = true; public static void e(String tag, String msg) {
if (isOpen) {
android.util.Log.e(tag, msg);
}
} public static void w(String tag, String msg) {
if (isOpen) {
android.util.Log.w(tag, msg);
}
} public static void d(String tag, String msg) {
if (isOpen) {
android.util.Log.d(tag, msg);
}
} public static void i(String tag, String msg) {
if (isOpen) {
android.util.Log.i(tag, msg);
}
} public static void v(String tag, String msg) {
if (isOpen) {
android.util.Log.v(tag, msg);
}
} public static void t(String tag, String msg) {
if (isOpen) {
android.util.Log.i(tag, msg + " : " + System.currentTimeMillis());
}
} public static void f(String fileName, String msg) {
if (isOpen) {
d(fileName, msg);
File fileDir = new File(Environment.getExternalStorageDirectory(), "/mlogs/");
File logFile = new File(fileDir, fileName); FileWriter fileOutputStream = null;
try {
if (!fileDir.exists()) {
if (!fileDir.mkdirs()) {
return;
}
}
if (!logFile.exists()) {
if (!logFile.createNewFile()) {
return;
}
}
fileOutputStream = new FileWriter(logFile, true);
fileOutputStream.write(msg);
fileOutputStream.flush();
} catch (Exception e) {
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
}
}
}
}
} public static void printStackTrace(Exception e) {
if (isOpen) {
e.printStackTrace();
}
} public static boolean isOpenLog() {
if (!isSDCardAvailable())
return false;
String path = Environment.getExternalStorageDirectory().getPath() + "/log.txt";
return (new File(path).exists());
} /**
* @TODO sdcard是否可用
*/
public static boolean isSDCardAvailable() {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return true;
}
return false;
} }
一般我们会创建一个常量TAG,如:
public static final String TAG = MainActivity.class.getSimpleName(); // "MainActivity"
然后在须要输出的时候调用Log的静态方法d[debug-蓝色]、i[info-绿色]、w[warn-黄色]、e[error-红色]、v[verbose-黑色]、t[time-带时间的info]进行输出
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName(); // "MainActivity" @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); android.util.Log.d("test", "我是測试信息");
Log.d(TAG, "我是debug測试信息");
Log.e(TAG, "我是error測试信息");
Log.w(TAG, "我是warn測试信息");
Log.i(TAG, "我是info測试信息");
Log.t(TAG, "我是time測试信息");
}
}
我们能够看到控制台输出的结果为这种
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGpsNDYxMjYwOTEx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
注意:此处的时间我并没有做本地化处理,直接使用的当前的毫秒数。主要是由于用的不多。如有须要能够做一点点转化。
在该类中,我们能够使用isOpen这个开关对程序的log信息做对应的关闭处理。
同一时候我们提供一个方法能够对打包成正式版本号的apk进行调试。即假设SDcard根文件夹下下存在log.txt文件时就输出调试信息打印。这里我们打开
private static boolean isOpen = isOpenLog();
此时控制台LogCat是没有不论什么Log的打印输出的,例如以下图:
然后我们新建一个log.txt然后放到SDcard根文件夹下就可以,此时我们能够看到熟悉的打印调试信息又出来了,这样就能够对打包完毕后的应用进行调试了。
以上我们已经成功实现一个可动态关闭的Log调试工具类。基本功能都已经成功实现,可是我认为还不够。
由于经过漫长的时间之后,这样的方法的劣势明显暴露。我们已然忘记当初打log的地方。寻找起来十分繁琐。因此接下来将介绍本篇的主角DebugLog。
我们先来试用一下。定义一个简单的方法
void mySecondFunc() {
DebugLog.v("simple log from mySecondFunc()");
}
此时观察LogCat,能够发现我们并没有做不论什么操作,即打印出所在类、方法、甚至调用的行号。依据对应信息就可以迅速定位到打印输出语句,此时就可以对它进行改动、删除等处理操作。
我们来看下源代码实现
/***
* This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
* software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. For more information, please
* refer to <http://unlicense.org/>
*/
package com.example.debuglog; import android.util.Log; /**
* @author J!nl!n
* @date 2014年11月19日
* @time 下午9:05:46
* @type DebugLog.java
* @todo 多功能调试工具类
*/
public class DebugLog {
/**
* Log输出所在类
*/
private static String className;
/**
* Log输出所在方法
*/
private static String methodName;
/**
* Log输出所行号
*/
private static int lineNumber; /**
* 是否可Debug状态
*
* @return
*/
public static boolean isDebuggable() {
return BuildConfig.DEBUG;
} /**
* 创建Log输出的基本信息
*
* @param log
* @return
*/
private static String createLog(String log) {
StringBuffer buffer = new StringBuffer();
buffer.append("[");
buffer.append(methodName);
buffer.append("()");
buffer.append(" line:");
buffer.append(lineNumber);
buffer.append("] ");
buffer.append(log); return buffer.toString();
} /**
* 取得输出所在位置的信息 className methodName lineNumber
*
* @param sElements
*/
private static void getMethodNames(StackTraceElement[] sElements) {
// 拆分去除.java
className = sElements[1].getFileName().split("\\.")[0];
methodName = sElements[1].getMethodName();
lineNumber = sElements[1].getLineNumber();
} public static void e(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.e(className, createLog(message));
} public static void i(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.i(className, createLog(message));
} public static void d(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.d(className, createLog(message));
} public static void v(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.v(className, createLog(message));
} public static void w(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.w(className, createLog(message));
} public static void wtf(String message) {
if (!isDebuggable())
return; getMethodNames(new Throwable().getStackTrace());
Log.wtf(className, createLog(message));
} }
原理事实上非常easy,在调用方法的地方得到该方法的调用栈(StackTraceElement)。然后就能够得出调用此方法所在位置的 类、方法、行号、文件名称等信息。这里补充说明一下。我们假设想要关闭打印输出进行打包时该怎样操作。通过分析能够看到例如以下代码
/**
* 是否可Debug状态
*
* @return
*/
public static boolean isDebuggable() {
return BuildConfig.DEBUG;
}
直接返回的是gen文件夹下BuildConfig.java文件里的DEBUG常量。假设想要关闭,改为false就可以。
/** Automatically generated file. DO NOT MODIFY */
package com.example.debuglog; public final class BuildConfig {
public final static boolean DEBUG = true;
}
扩展:
我们能够依据这个原理来查看源代码中方法被调用的位置。比如,我们须要查看Activity的onCreate方法在哪里被调用便能够使用此方法实现目的。
/**********************************************************
* @文件名:MainActivity.java
* @创建时间:2014年11月19日 下午9:30:06
* @改动历史:2014年11月20日
**********************************************************/
package com.example.debuglog;
import android.app.Activity;
import android.os.Bundle;
/**
* @author J!nl!n
* @date 2014年11月19日
* @time 下午9:30:06
* @type MainActivity.java
* @todo
*/
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName(); // "MainActivity" void myFunc() {
android.util.Log.i(TAG, "my message");
}
void mySecondFunc() {
DebugLog.v("simple log from mySecondFunc()");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); android.util.Log.d("test", "我是測试信息"); Log.d(TAG, "我是debug測试信息");
Log.e(TAG, "我是error測试信息");
Log.w(TAG, "我是warn測试信息");
Log.i(TAG, "我是info測试信息");
Log.t(TAG, "我是time測试信息"); myFunc();
mySecondFunc();
getMethodNames(new Throwable().getStackTrace());
DebugLog.i("onCreate的调用位置: " + className + "-" + methodName + "-" + lineNumber);
} private static String className;
private static String methodName;
private static int lineNumber; /**
* 取得输出所在位置的信息 className methodName lineNumber
*
* @param sElements
*/
private void getMethodNames(StackTraceElement[] sElements) {
className = sElements[1].getFileName().split("\\.")[0];
methodName = sElements[1].getMethodName();
lineNumber = sElements[1].getLineNumber();
} @Override
protected void onResume() {
super.onResume(); DebugLog.v("v log");
DebugLog.w("w log");
DebugLog.wtf("wtf log");
}
}
观察LogCat能够得到打印结果
我们清楚的知道onCreate方法是在Activity类中performCreate方法中调用的。其所在位置在5008行,但当我满心欢喜打开Activity源代码通过快捷键Ctrl+L定位到5008行发现,这结果尼玛绝对是在坑我
因为我使用的是API为16的4.1.1模拟器。得到的结果为5008行,但我关联的源代码为API19,因此得到错误的结果属于正常情况。
改动关联API16的源代码之后发现果然是5008行调用的onCreate方法
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGpsNDYxMjYwOTEx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
兴许打开API为19的4.4.4的模拟器执行指挥得到的结果为5231行。
或许有人会好奇为什么这次为什么这次会缺少诸例如以下面的日志信息。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGpsNDYxMjYwOTEx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
这是由于我们换了模拟器之后,默认的SDcard根文件夹下是没有log.txt文件的所以debug开关是关闭状态所以不会信息打印,这更进一步说明其非常好的有用性。
再一次通过快捷键Ctrl+L高速定位到5231行,我们发现结果全然正确
本篇到此就结束。
版权声明:本文博客原创文章。博客,未经同意,不得转载。