在开发和应用的开发和调试过程中难免会发现故障的过程中。我相信很多做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. .NET开源 FAQ

    Microsoft至2014年11月12日本(PST)公布.NET开源.一个"隐居"商业帝国也迎来"改革开放".. . Q1:为什么要开放源码? Ans:由于. ...

  2. HDU1071 The area 【积分】

    The area Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  3. CF 439D(251D题)Devu and his Brother

    Devu and his Brother time limit per test 1 second memory limit per test 256 megabytes input standard ...

  4. 我写了一起 Makefile(一)

    我写了一起 Makefile  陈皓 概述—— 什么是makefile?也许非常多Winodws的程序猿都不知道这个东西,由于那些Windows的IDE都为你做了这个工作.但我认为要作一个好的和pro ...

  5. 用EnableMenuItem不能使菜单变灰的原因

    为何不能Disable菜单项     问:我有一个工具button在WM_COMMAND消息是这样做的:         CMenu   *pMenu   =   GetMenu();     pMe ...

  6. 《Javascript高级程序设计》读书笔记之bind函数详解

    为什么需要bind var name = "The Window"; var object = { name: "My Object", getNameFunc ...

  7. 【NIO】dawn在buffer用法

    网络编程,buffer它用于数据传输到网络上的集线器应用程序,不用说,一个重要的线.提到buffer我不能说什么零拷贝,buffer什么内存管理,在dawn在,基于directbuffer再次能够实现 ...

  8. Java调用Lua(转)

    Java 调用 Lua app发版成本高,覆盖速度慢,覆盖率页低.一些策略上的东西如果能够从服务端控制会方便一些.所以考虑使用Lua这种嵌入式语言作为策略实现,Java则是宿主语言. 总体上看是一个模 ...

  9. 用于编译cm-12.0 的 local_manifest.xml文件

    将代码保存为 romservice.xml文件 <?xml version="1.0" encoding="UTF-8"?> <manifes ...

  10. Android异步任务

    本文主要探讨Android平台提供的各种异步载入机制,包括它们的适用场景.用法等. 1. AsynTask AsynTask适用于最长能够持续几秒钟的短时间的操作,对于长时间的操作,建议使用java. ...