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

需求:

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的。


源码下载点击打开链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值