在开发和应用的开发和调试过程中难免会发现故障的过程中。我相信很多做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行,我们发现结果全然正确

本篇到此就结束。

源代码

版权声明:本文博客原创文章。博客,未经同意,不得转载。

使用更清晰DebugLog开发和调试工具的更多相关文章

  1. 前端开发必备调试工具(Chrome的F12自带的功能和firebug插件差不多)

    前端开发必备调试工具(Chrome的F12自带的功能和firebug插件差不多) 一.总结 Chrome的F12自带的功能和firebug插件差不多 二.前端开发必备调试工具 在前端开发中我们经常会要 ...

  2. IE, FF, Safari前端开发常用调试工具

    一些前端开发 IE 中的常用调试工具: Microsoft Script Debugger —— Companion.JS need to install this Companion.JS —— J ...

  3. 自己开发前端调试工具:Gungnir

    文章目录 1. 界面介绍 2. 项目资源管理界面 3. 文本编辑器功能 4. 代理功能 4.1. 自动下载线上文件 4.2. 使用本地已有文件 4.3. 代理整个目录 4.4. 执行文件内容后返回结果 ...

  4. 开发人员调试工具Chrome Workspace

    Workspace是个什么样的东西呢?他可以在开发人员工具中调试改动js或者css同一时候自己主动保存文件.可以避免开发人员在工具中调试好,再到编辑器中改动一次代码的反复操作,可以提高一定的效率 配置 ...

  5. 前端开发Chrome调试工具

    Chrome浏览器提供了一个非常好用的调试工具,可以用来调试我们的HTML结构和CSS样式. 1.的打开调试工具 打开Chrome浏览器,按下F12键或点击页面空白,点击检查 2.使用调试工具 (1) ...

  6. linux后台开发常用调试工具

    一.编译阶段         nm                 获取二进制文件包含的符号信息 strings           获取二进制文件包含的字符串常量 strip             ...

  7. Vue开发与调试工具

    vscode: Visual Studio Code https://code.visualstudio.com/Download 可以下载各个版本的,Windows/ Debian /Mac 等 W ...

  8. Linux驱动开发常用调试工具---之内存读写工具devmem和devkmem【转】

    转自:https://blog.csdn.net/gatieme/article/details/50964903 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  9. 干货--微信公众平台客户端调试工具-初试WPF开发

    本工具可以由任意一个开发微信公众平台的开发者使用,虽然它本身使用WPF(C#)开发的,但是并不受你想调试的服务所用的语言的影响. 之前一直在做微信公众平台开发,客户端调试是必须做的事情,一直以来都是用 ...

随机推荐

  1. Xcode6在10.9.4上面crash解决

    具体请看我的evernote 这里: 在10.9.4系统上面直接安装xcode6的beta3.和平时一样, 1.将beta3拖拽到application文件夹中. 2.等待copy完毕,执行xcode ...

  2. 解决set /p yn= 接受键盘输入导致ECHO 处于关闭状态的问题

    今天写了一个自动更新程序的批处理脚本,但是有个变量一直赋值有问题.弄了一个下午终于找到原因及解决方法: ----转载要说明来自:博客园--邦邦酱好 哦 有问题的代码如下: @echo off echo ...

  3. Eclipse常用设置汇总

    设置编码: . 设置字体: 依次展开 Window->Preferences->Java->Code Style->Formatter  在右边窗口中找到Edit ·找到Lin ...

  4. hdu 5035 概率论

    n服务形式,各服务窗口等候时间指数公布,求所需的等待时间. 解: 相两点:首先,等到轮到他,然后就是送达时间. 潜伏期期望每个表单1/ki(1/ki,宣布预期指数公式).总的等待时间预期1/(求和ki ...

  5. 修改linux系统时间、rtc时间以及时间同步

    修改linux的系统时间用date -s [MMDDhhmm[[CC]YY][.ss]] 但是系统重启就会从新和硬件时钟同步. 要想永久修改系统时间,就需要如下命令:hwclock hwclock - ...

  6. Java 执行四则运算

    四种基本的操作原理是将被转换成后缀缀表达式表达.然后计算. 转换思路和原则.可以参考将中缀表达式转化为后缀表达式 import java.math.BigDecimal; import java.ut ...

  7. linux下查找某个文件

    参考http://blog.csdn.net/gray13/article/details/6365654 一.通过文件名查找法: 举例说明,假设你忘记了httpd.conf这个文件在系统的哪个目录 ...

  8. SqlServer保留几位小数的两种做法

    SqlServer保留几位小数的两种做法   数据库里的 float momey 类型,都会精确到多位小数.但有时候 我们不需要那么精确,例如,只精确到两位有效数字. 解决: 1. 使用 Round( ...

  9. Spring常见问题解决办法汇总

    解决The prefix 'context' for element 'context:component-scan' is not bound<beans xmlns="http:/ ...

  10. SQL Server 性能调优培训引言

    原文:SQL Server 性能调优培训引言 大家好,这是我在博客园写的第一篇博文,之所以要开这个博客,是我对MS SQL技术学习的一个兴趣记录. 作为计算机专业毕业的人,自己对技术的掌握总是觉得很肤 ...