问题引入
其实之前有写过一个简单的日志框架
(原创)分享自己写的几个工具类(十)文件日志记录工具
但是这个工具类有几个问题
1:在主线程执行写日志功能,有一点不太好
2:改造不好,可能存在多个线程去读写同一个文件的问题
3:日志没有存储容量上限
4:日志无法压缩,无法适应“压缩后传送到服务器”的需求
所以我在原来工具类的基础上,做了一下优化和改造
于是一个新的日志框架诞生了!!!
新的日志工具类,主要就是解决这几个问题的
文章尾部会贴出主代码和使用方式
现在开始对这个工具类进行一个基础介绍:
问题1
首先,第一个问题,采用了线程池的方式解决
private static ExecutorService executor = Executors.newFixedThreadPool(5);
创建了一个拥有五个线程的线程池
在记录信息时执行线程池的submit方法
在子线程记录日志
问题2
对于问题2,解决方式就是对线程进行加锁
保证一个线程在执行写入操作时,其他线程不可以操作该文件
private static Lock lock = new ReentrantLock();
调用lock.lock()和lock.unlock()方法
来执行加锁和解锁
问题3
问题3,日志没有存储容量上限
这边定义了一个容量上线
public static final int DEFAULT_CAPACITY = 5120;//默认容量 单位kb 5120即5M
同时定义了两个缓存日志的文件lionlog1.txt和lionlog1.txt
他们容量上限都是5M,当然这个上限你可以灵活改动
他们的逻辑如下
写入文件时先判断lionlog1是否写满了,也就是超出容量5M了
如果没写满,就写lionlog1,并且记录好这次写的文件是lionlog1
如果写满了,就写lionlog2,并且记录好这次写的文件是lionlog2
代码如下:
if ((file1.length() / 1024) >= DEFAULT_CAPACITY) {
//file1满了
seleFile = file2;
isfile1 = false;
} else {
//表示file1没满
seleFile = file1;
isfile1 = true;
}
得到了要写入的文件,进行写入即可
写完后进行容量检查
容量检查逻辑如下:
先判断这些写入的是那个文件
是lionlog1还是lionlog2
如果这次写入的是lionlog1,就判断lionlog1是否超出容量了
如果lionlog1超出容量,就删除lionlog2文件,然后结束
这样做,下次进来,系统就会开始写lionlog2文件
如果这次写入的是lionlog2,就判断lionlog2是否超出容量了
如果lionlog2超出容量,就删除lionlog1文件,然后结束
这样做,下次进来,系统就会开始写lionlog2文件
这样,A文件满了,就清空B
下次进来后就可以写B
直到B满了,再去清空A
下次进来就一直写A
循环往复
代码如下
if (isfile1) {
//说明使用的是file1存储日志,判断是否超出存储范围
if ((file1.length() / 1024) >= DEFAULT_CAPACITY) {
//file1满了,删除file2
file2.delete();
}
} else {
//说明使用的是file2存储日志,判断是否超出存储范围
if ((file2.length() / 1024) >= DEFAULT_CAPACITY) {
//file2满了,删除file1
file1.delete();
}
}
这套逻辑也可以做成链式的
比如有n个日志缓存文件a1,a2,a3,a4…
这样a1满了,就清空a2
下次进来写a2
直到a2满了,就清空a3
下次进来写a2
等到最后一个a(n)满了
就清空a1
重新在a1这里写
问题4
日志无法压缩,无法适应“压缩后传送到服务器”的需求
这个问题,需要用到我之前写的一个文件压缩的工具类ZipUtils
(原创)分享一个文件以及文件夹压缩工具类
我这个日志框架内部就是用这个工具类实现了文件压缩
同时对外部提供了回调接口
方便外部在文件压缩完成后,对压缩后的文件进行一系列的操作
比如上传服务器,备份,删除等等
源码
下面先贴出这个日志框架的源码,然后介绍它的使用方法
/**
* @introduce : 日志框架
* <p>
* creattime : 2021/8/11
* <p>
* author : xiongyp
**/
public class LionLog {
//是否保存日志
private static boolean isLog = false;
//存储路径1
private static String mStrU1;
//存储路径2
private static String mStrU2;
//要压缩的文件夹
private static String toZipDir;
//压缩后的压缩包路径+名称
private static String toZipFilename;
public static final int DEFAULT_CAPACITY = 5120;//默认容量 单位kb 5120即5M
public static void init(Context context, boolean isopen) {
isLog = isopen;
mStrU1 = context.getExternalFilesDir(null).getAbsolutePath() + "/Lionlog/lionlog1.txt";
mStrU2 = context.getExternalFilesDir(null).getAbsolutePath() + "/Lionlog/lionlog2.txt";
toZipDir = context.getExternalFilesDir(null).getAbsolutePath() + "/Lionlog";
toZipFilename = context.getExternalFilesDir(null).getAbsolutePath();
}
/*
创建一个拥有5个线程的线程池
*/
private static ExecutorService executor = Executors.newFixedThreadPool(5);
private static Lock lock = new ReentrantLock();
/**
* @param context
* @param msg 记录信息
*/
public static void writeMsgIntoFile(Context context, final String msg) {
if (context == null || isNullOrNil(msg) || isNullOrNil(mStrU1) || isNullOrNil(mStrU2) || !isLog) {
return;
}
executor.submit(new Runnable() {
@Override
public void run() {
lock.lock();
try {
// Log.d("LionLog", "线程名字:" + Thread.currentThread().getName());
long startTime = System.currentTimeMillis(); //起始时间
File seleFile;
boolean isfile1;
File file1 = new File(mStrU1);
File file2 = new File(mStrU2);
if (!file1.exists() && !file1.getParentFile().exists()) {
file1.getParentFile().mkdirs();
}
if (!file2.exists()) {
file2.createNewFile();
}
// Log.d("LionLog", "前大小:" + (file1.length() / 1024));
if ((file1.length() / 1024) >= DEFAULT_CAPACITY) {
//file1满了
seleFile = file2;
isfile1 = false;
} else {
//表示file1没满
seleFile = file1;
isfile1 = true;
}
StringBuilder sb = new StringBuilder();
sb.append(getFormatTime())
.append(msg)//日志信息
.append("\n\n");//加上双换行
FileWriter fw = new FileWriter(seleFile.getAbsolutePath(), true);
fw.write(sb.toString());
fw.flush();
fw.close();
// Log.d("LionLog", "后大小:"+(file1.length()) );
//检查容量
checkCapacity(isfile1, file1, file2);
notifySystemToScan(context, seleFile);
long endTime = System.currentTimeMillis(); //结束时间
long runTime = endTime - startTime;
// Log.d("LionLog", String.format("日志写入,此次耗时 %d ms", runTime));
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
}
/**
* 检查容量,判断是否要转储
*
* @param isfile1
* @param file1
* @param file2
*/
private static void checkCapacity(boolean isfile1, File file1, File file2) {
if (isfile1) {
//说明使用的是file1存储日志,判断是否超出存储范围
if ((file1.length() / 1024) >= DEFAULT_CAPACITY) {
//file1满了,删除file2
file2.delete();
}
} else {
//说明使用的是file2存储日志,判断是否超出存储范围
if ((file2.length() / 1024) >= DEFAULT_CAPACITY) {
//file2满了,删除file1
file1.delete();
}
}
}
/**
* 压缩文件
*/
public static void zipFile() {
executor.submit(new Runnable() {
@Override
public void run() {
lock.lock();
FileOutputStream fos1 = null;
try {
/** 测试压缩方法 单个文件夹压缩 */
String zipFileName = "/Lionlog_" + new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss").format(new Date()) + ".zip";
String toZipFilePath = toZipFilename + zipFileName;
fos1 = new FileOutputStream(new File(toZipFilePath));
ZipUtils.toZip(toZipDir, fos1, true);
// Log.d("SettingsActivity", "压缩结果" + toZipFilePath);
mHandler.post(new Runnable() {
@Override
public void run() {
if (onZipSuccListener != null) {
//传回压缩结果
onZipSuccListener.onZipresult(toZipFilePath,zipFileName);
}
}
});
/** 测试压缩方法 多文件压缩 */
// List<File> fileList = new ArrayList<>();
// fileList.add(new File(mStrU1));//文件列表1
// fileList.add(new File(mStrU2));//文件列表2
// FileOutputStream fos2 = new FileOutputStream(new File(toZipFilename));//这里写压缩后的压缩包路径以及名称
// ZipUtils.toZip(fileList, fos2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
}
private static Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
private static OnZipSuccListener onZipSuccListener;
public static void setOnZipSuccListener(OnZipSuccListener onZipSuccListener) {
LionLog.onZipSuccListener = onZipSuccListener;
}
public interface OnZipSuccListener {
/**
*
* @param toZipFilePath 全路径
* @param toZipFileName 文件名
*/
void onZipresult(String toZipFilePath,String toZipFileName);
}
/**
* 得到格式化时间
*
* @return
*/
private static String getFormatTime() {
return new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss:").format(new Date());
}
/**
* 扫描sd卡,进行更新
*
* @param context
* @param file
*/
private static void notifySystemToScan(Context context, File file) {
if (context == null || file == null || !file.exists()) {
return;
}
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(file);
intent.setData(uri);
context.sendBroadcast(intent);
}
/**
* 判断字符串是否为空
*
* @param str
* @return
*/
private static boolean isNullOrNil(String str) {
if (str == null || str.length() == 0) {
return true;
}
return false;
}
}
使用
其实使用很简单,首先在你的application里面这样写:
LionLog.init(this,true);
true代表开启日志记录功能,false就不会去记录日志了
记录日志,调用这个方法即可
LionLog.writeMsgIntoFile(this,"MainActivity记录一条日志:");
如果要对日志进行压缩
LionLog.zipFile();
当然,如果你需要压缩结束后的回调
LionLog.setOnZipSuccListener(new LionLog.OnZipSuccListener() {
@Override
public void onZipresult(String toZipFilePath, String toZipFileName) {
Log.d("MainActivity", "压缩成功");
Log.d("MainActivity", "全路径:"+toZipFilePath);
Log.d("MainActivity", "压缩包名称:"+toZipFileName);
}
});
在代码里预先设置这个监听,就可以获取压缩成功后的压缩包全路径以及压缩包的名称了
工具类里是采用当前时间来命名压缩包的名称的,你也可以自己改造
关于这个日志框架,这次就先介绍到这里
以后有更多的提升
也会继续更新的
也欢迎大家给出提升的建议和思路,谢谢啦~