(原创)手写一个安卓日志框架

问题引入

其实之前有写过一个简单的日志框架
(原创)分享自己写的几个工具类(十)文件日志记录工具
但是这个工具类有几个问题
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);
            }
        });

在代码里预先设置这个监听,就可以获取压缩成功后的压缩包全路径以及压缩包的名称了
工具类里是采用当前时间来命名压缩包的名称的,你也可以自己改造

关于这个日志框架,这次就先介绍到这里
以后有更多的提升
也会继续更新的
也欢迎大家给出提升的建议和思路,谢谢啦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AOP(Aspect Oriented Programming)是一种面向切面编程的编程范式,它可以在程序运行时动态地将代码插入到指定方法的前面、后面或者中间,从而实现对方法的增强。 下面是手写一个简单的AOP框架的步骤: 1. 定义注解 定义一个自定义注解,用于标注需要增强的方法。例如: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { } ``` 2. 定义切面 定义一个切面类,该类包含需要增强的方法以及增强逻辑。例如: ```java public class MyAspect { public void before() { System.out.println("before"); } public void after() { System.out.println("after"); } } ``` 3. 定义代理类 定义一个代理类,该类通过反射机制获取需要增强的方法以及切面类中的增强逻辑,并在方法执行前后调用切面类中的增强逻辑。例如: ```java public class MyProxy { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes()); if (targetMethod.isAnnotationPresent(MyAnnotation.class)) { MyAspect aspect = new MyAspect(); aspect.before(); Object result = method.invoke(target, args); aspect.after(); return result; } else { return method.invoke(target, args); } }); } } ``` 4. 使用代理类 通过代理类获取代理对象,并使用该对象调用需要增强的方法。例如: ```java public class Main { public static void main(String[] args) { MyClass myClass = new MyClass(); MyClass myProxy = (MyClass) MyProxy.getProxy(myClass); myProxy.method(); } } class MyClass { @MyAnnotation public void method() { System.out.println("method"); } } ``` 当调用代理对象的`method()`方法时,会先调用`MyAspect`中的`before()`方法,然后调用原始对象`MyClass`中的`method()`方法,最后再调用`MyAspect`中的`after()`方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值