您的位置:首页 > 移动开发 > Android开发

Android一行代码实现将Log写入文件(可配置是否输出Log,测试包可直接用于发布)

2017-01-03 11:12 681 查看
需求:

1.在测试阶段,也可以留下Log,出问题方便查看。给qa测试或者晚上放着手机跑App等

2.测试包不需要改代码,可以直接发布,正常用户因为没有配置文件(配置文件下面的内容会介绍),所以看不到Log,保证了信息的安全性

因为笔者前不久在做一个项目,需求是在用户屏幕熄灭一个小时后,开始点亮屏幕做任务。

所以选在白天开发完后,晚上放着几台手机跑。第二天如果要想知道昨晚运行是否正常,最好的办法就是看Log,把Log写入文件,这是最直观的。

关于6.0或之后版本的手机休眠后不可唤醒,如何主动点亮屏幕,怎样才能保持后台运行不被杀死等内容,笔者会在之后的博客里面进行讲解。

不废话了,直接上代码

package com.chenjian.logutil;

import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;

/**
* Log使用类
*/
public class LogUtil {

private static final int VERBOSE = 0;
private static final int DEBUG = 1;
private static final int INFO = 2;
private static final int WARNING = 3;
private static final int ERROR = 4;
private static final int NO_LOG = 5;
private static final String LOG_FILE = LogManager.getInstance().getLogFilePath();

private static LogUtil mInstance = null;
private static int LOG_LEVEL = NO_LOG;
private static boolean logFileEnable = false;

static {
try {
// 通过SD卡配置文件启用日志
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// path为配置文件的路径
String path = Environment.getExternalStorageDirectory() + "/config.properties";
if ((new File(path).exists())) {
InputStream inputStream = new FileInputStream(path);
Properties properties = new Properties();
properties.load(inputStream);
if ("true".equals(properties.getProperty("logcat"))) {
LOG_LEVEL = VERBOSE;
}
if ("true".equals(properties.getProperty("filelog"))) {
logFileEnable = true;
}
inputStream.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

private static LogUtil getInstance() {
if (mInstance == null) {
synchronized (LogUtil.class) {
if (mInstance == null) {
mInstance = new LogUtil();
}
}
}
return mInstance;
}

// verbose
public static void v(String tag, String msg) {
if (VERBOSE < LOG_LEVEL)
return;
if (msg == null)
return;
Log.v(tag, msg);
write("VERBOSE", getInstance().getPrefixName(), msg);
}

public static void v(String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
v(getInstance().getPrefixName(), msg);
}

// debug
public static void d(String tag, String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
Log.d(tag, msg);
write("DEBUG", getInstance().getPrefixName(), msg);
}

public static void d(String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
d(getInstance().getPrefixName(), msg);
}

// info
public static void i(String tag, String msg) {
if (INFO < LOG_LEVEL)
return;
if (msg == null)
return;
Log.i(tag, msg);
write("INFO", getInstance().getPrefixName(), msg);
}

public static void i(String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
i(getInstance().getPrefixName(), msg);
}

// warning
public static void w(String tag, String msg) {
if (WARNING < LOG_LEVEL)
return;
if (msg == null)
return;
Log.w(tag, msg);
write("WARNING", getInstance().getPrefixName(), msg);
}

public static void w(String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
w(getInstance().getPrefixName(), msg);
}

public static void w(String tag, String msg, Throwable tr) {
if (WARNING < LOG_LEVEL)
return;
if (msg == null)
return;
Log.w(tag, msg, tr);
write("WARNING", getInstance().getPrefixName(), msg);
}

// error
public static void e(String tag, String msg) {
if (ERROR < LOG_LEVEL)
return;
if (msg == null)
return;
Log.e(tag, msg);
write("ERROR", getInstance().getPrefixName(), msg);
}

public static void e(String msg) {
if (DEBUG < LOG_LEVEL)
return;
if (msg == null)
return;
e(getInstance().getPrefixName(), msg);
}

/**
* 写到文件中的log的前缀,如果因为混淆之类的原因而取不到,就返回"[ minify ]"
*
* @return prefix
*/
private String getPrefixName() {
StackTraceElement[] sts = Thread.currentThread().getStackTrace();
if (sts == null || sts.length == 0) {
return "[ minify ]";
}
try {
for (StackTraceElement st : sts) {
if (st.isNativeMethod()) {
continue;
}
if (st.getClassName().equals(Thread.class.getName())) {
continue;
}
if (st.getClassName().equals(this.getClass().getName())) {
continue;
}
if (st.getFileName() != null) {
return "[ " + Thread.currentThread().getName() +
": " + st.getFileName() + ":" + st.getLineNumber() +
" " + st.getMethodName() + " ]";
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "[ minify ]";
}

/**
* 追加文件:使用FileWriter
*
* @param level   等级
* @param prefix  前缀
* @param content 内容
*/
private static void write(String level, String prefix, String content) {
if (!logFileEnable)
return;
try {
// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
FileWriter writer = new FileWriter(LOG_FILE, true);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
String time = sdf.format(new Date());
writer.write(time + ": " + level + "/" + prefix + ": " + content + "\n");
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


这个类就是一个工具类,可以使用其中的v d i w e 方法,输出log。

从上到下来看,先是定义了一些常量,为Log等级。然后是保存log的文件,log开关等。

再看static域,通过sd卡根目录下面的一个配置文件来判断是否打开log,配置文件为.properties类型,这种文件,我相信使用as做开发的朋友并不会陌生,新建一个工程,目录下面就有好几个.properties文件,比如gradle.properties。这种文件非常简单,一行一个单元,属性=值,此类文件的具体内容本篇不再介绍。

在static域里,我们根据配置文件,来判断是否打开log,LOG_LEVEL为log的等级,只有他打开的时候,才会输出log,包括控制台和文件的log。logFileEnable为log输出到文件的开关,如果只打开LOG_LEVEL而logFileEnable没打开,只会把log输出到控制台。总的来说,LOG_LEVEL为总开关,logFileEnable为子开关。

看看write方法,就是把log输出到文件里面,要注意,输出到文件里面的Log要添加前缀,因为此处不像控制台一样,系统和开发工具会帮你输出和显示一些相信信息,比如log等级,时间,类,方法,行数之类的信息。

gerPrefixName就是获取部分前缀信息,获取不到的话,可能被混淆了,就输出 [ minify ]

再回过头来看看LOG_FILE这个变量,其为log文件路径,由LogManager里面获取,所以看看LogManager的代码

package com.chenjian.logutil;

import java.io.File;

/**
* Log逻辑管理类
* <p>
* Created by ChenJian
* 2016.10.8 14:22:22.
*/

public class LogManager {

private static volatile LogManager mInstance;
private String logFilePath;

private LogManager() {

}

public static LogManager getInstance() {
if (mInstance == null) {
synchronized (LogManager.class) {
if (mInstance == null) {
mInstance = new LogManager();
}
}
}
return mInstance;
}

/**
* 初始化log配置
*
* @param logFilePath log文件路径,必须是完整的全路径
*/
public void init(String logFilePath) {
this.logFilePath = logFilePath;
createLogFile();
}

private boolean createLogFile() {
boolean ret = false;
File file = new File(logFilePath);
try {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
ret = file.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}

String getLogFilePath() {
return logFilePath;
}
}


就一个功能,通过init方法由外部传入log文件路径。如果开发者觉得麻烦不想用这个类,可以直接把log文件写到LogUtil类里面,做一下初始化,就行了。

最后看看使用

package com.chenjian.logutil;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;

public class MainActivity extends Activity {

/**
* 主路径
*/
private static final String MAIN_DIR = Environment.getExternalStorageDirectory() + "/ChenJian/CsdnBlog/LogUtil";

/**
* 保存Log的文件
*/
private static final String LOG_FILE = MAIN_DIR + "/log.txt";

private final static String LOG_FOR_QA = "[log4qa] ";
private final static String LOG_FOR_DADIAN = "[log4dadian] ";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

LogManager.getInstance().init(LOG_FILE);

LogUtil.d(LOG_FOR_QA, "发发恭子的博客");
LogUtil.e(LOG_FOR_DADIAN, "http://blog.csdn.net/qq_296216078");
}
}


初始化一下log文件路径,就可以尽情的输出log了。

运行程序,结果如下:





并没有输出log。那是因为没有放入配置文件。我们来看看配置文件的内容,



是如此的简单。

把配置文件放到sd卡根目录,再运行看看





可以看到控制台和文件都有log输出。

当然了,有人会觉得使用配置文件非常麻烦,但我觉得一台手机只要放一次,能使用一辈子,并不是太麻烦。

你如果要想在文件里面保存log,除了放配置文件,就只能改代码了,改代码的话,相当于要apk包改变了,对于正规的测试发布流程来说,包修改后,就要重新测过一遍。因此,使用改代码的方式的话,你的最后一次完整测试,也就是发布前的那一次测试,是看不到log的。

源码下载点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: