Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,原因是它的很多弊端,比如不可控制,所有的日志都会在项目上线后照常打印,从而降低运行效率;又或者不能将日志记录到本地文件,一旦打印被清除,日志将再也找不回来;再或者打印的内容没有Tag区分,你将很难辨别这一行日志是在哪个类里打印的。因此我们在日常开发的过程中通常是自己编写一个日志工具类。
由于是一个工具类,因此考虑用单例模式实现。通常我们会用采用以下代码:
public class LogUtil {
private static LogUtil sLogUtil;
public final int DEBUG = 0;
public final int INFO = 1;
public final int ERROR = 2;
public int level = DEBUG;
private LogUtil() {
}
public static LogUtil getInstance() {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
public void debug(String msg) {
if (DEBUG >= level) {
System.out.println(msg);
}
}
public void info(String msg) {
if (INFO >= level) {
System.out.println(msg);
}
}
public void error(String msg) {
if (ERROR >= level) {
System.out.println(msg);
}
}
}
但是,该实现方案有一个问题,那就是多线程的情况下无法保证线程安全。因此改进如下:
public synchronized static LogUtil getInstance() {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
现在getInstance方法上加了一个synchronized,那么我每次去执行getInstace方法的时候都会受到同步锁的影响,但是这样运行的效率会降低,其实只需要在第一次创建LogUtil实例的时候加上同步锁就好了。因此进一步改进如下:
首先将关键字从方法声明中去除,把它加入到方法体当中:
public static LogUtil getInstance() {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
}
这样效果是和直接在方法上加synchronized完全一致的。然后在synchronized的外面再加一层判断,如下所示:
public static LogUtil getInstance() {
if (sLogUtil == null) {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
}
}
return sLogUtil;
}
代码改成这样之后,只有在sLogUtil还没被初始化的时候才会进入到第3行,然后加上同步锁。等sLogUtil一但初始化完成了,就再也走不到第3行了,这样执行getInstance方法也不会再受到同步锁的影响,效率上会有一定的提升。
这种方法就叫做双重检查锁定(Double-Check Locking)。
当然,结合Android日志打印的特殊需求,我们再对该日志工具类做一些定制化的改写。让它可以输出线程和类的信息,并且让它只在测试环境下才进行打印。最终版本如下:
public class LogUtil {
private static final String TAG = "LogUtil";
private static boolean DEBUG = BuildConfig.DEBUG;
private static LogUtil sLogUtil;
private LogUtil() {}
public static LogUtil getInstance() {
if (sLogUtil == null) {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
}
}
return sLogUtil;
}
public static void info(Object msg) {
if (DEBUG) {
Log.i(TAG, formatLog(msg.toString()));
}
}
public static void debug(Object msg) {
if (DEBUG) {
Log.d(TAG, formatLog(msg.toString()));
}
}
public static void error(Object msg) {
if (DEBUG) {
Log.e(TAG, formatLog(msg.toString()));
}
}
private static String getFuncName() {
try {
StackTraceElement[] sts = Thread.currentThread().getStackTrace();
if (sts != null) {
for (StackTraceElement st : sts) {
if (st.isNativeMethod()) {
continue;
}
if (st.getClassName().equals(Thread.class.getName())) {
continue;
}
if (st.getClassName().equals(LogUtil.class.getName())) {
continue;
}
return "Thread:" + Thread.currentThread().getName() + ", at " + st.getClassName() + "." + st.getMethodName()
+ "(" + st.getFileName() + ":" + st.getLineNumber() + ")";
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String formatLog(String msgStr) {
return getFuncName() + "----" + msgStr;
}
}