Intellij插件开发:MonkeyMaster插件的实现(三)——写入日志的线程处理

来源:互联网 发布:网络理财被骗怎么办 编辑:程序博客网 时间:2024/04/29 19:35

【转载请注明出处】
笔者:DrkCore (http://blog.csdn.net/DrkCore)
原文链接:(http://blog.csdn.net/drkcore/article/details/56680079)

一、 线程使用规范

我想大部分从事过Android开发的朋友在第一次在IntelliJ Idea插件开发文档中看到关于线程使用规范时,都会像笔者一样困惑不已:

IntelliJ Platform SDK DevGuide: General Threading Rules

Reading data is allowed from any thread. Reading data from the UI thread does not require any special effort. However, read operations performed from any other thread need to be wrapped in a read action by using ApplicationManager.getApplication().runReadAction() or, shorter, ReadAction.run/compute.

允许在任意线程读取数据。从UI线程中读取数据并不要求任何额外的操作,但在其他线程你需要将读取的逻辑包裹在 ApplicationManager.getApplication().runReadAction() 中才行,你也可以使用 ReadAction.run/compute 来简化代码。

Writing data is only allowed from the UI thread, and write operations always need to be wrapped in a write action with ApplicationManager.getApplication().runWriteAction() or, shorter, WriteAction.run/compute

只允许在UI线程写入数据,并且写入数据的逻辑必须包裹在 ApplicationManager.getApplication().runWriteAction() 才能执行,你也可以使用 WriteAction.run/compute 来简化你的代码。

“不要在主线程中执行耗时的操作”是我们在开发Android应用时应当牢记的准则。在 Intellij Idea 插件开发中显然不是这样的,按照文档我们就该将写入这样的耗时操作放在 UI 线程,饶是如此,如果你一个写入操作执行的时间太长的话界面照样是会卡死的。

二、 如何写入日志

这里就会有一个问题:

Monkey 测试的事件是随机的要想测出问题就要花费大量的时间,结果就是日志通常很大。而且有些同行甚至喜欢下班前执行一次超长的测试任务第二天上班再查看日志,这要是拿起键盘就是干的话—-你的插件把人家的 IDE 卡了一个晚上你好意思吗?

将日志保存到内存中待任务结束后再一次性写入磁盘是可以解决问题,但是一旦中途发生了异常就有可能导致日志丢失。当然我们也可以设计成每输出比如10K大小的日志数据时就执行一次保存操作,但开发起来就会比较纠结。

有没有好点的解决方案呢?当然有:

秘技:使用 > 符号将命令的输出转存到指定路径的文件中!

想必熟悉命令行或者终端的朋友早就猜到这个方法了。

该方法对 Win / Linux / os X 系统都是有效的,并且实际写入逻辑是由操作系统在另一个 进程 执行的,运行的效果肯定是棒棒的,开发起来也很简单,堪称完美的解决方案!

三、 实时显示日志

按照笔者最开始的规划这个插件需要实现的一个重要功能就是实时显示日志。

通过命令行的技巧我们成功解决了日志输出的问题。按照官方文档我们可以在任何线程执行读取操作,因此我们只要新开一个线程用于读取日志文件接口就可以解决问题。

主要会遇到的问题就是,通常情况下磁盘的读取速度远比写入速度要来的快,因而在读取的线程中需要在进程未结束之前等待。这个倒不难处理,比较成问题的是:

这个功能真的有必要存在吗?

如上文所言 Monkey 测试的日志量总是很庞大的,将日志等级提到最高并且设定个大点的事件数量就能让控制台一片雪崩,同时在没办法使用”查找”功能的控制台里想要从数万行乃至数十万行的日志中抽取出一点有用的异常信息也几乎是不可能的。

所以是的,比起日志的实际意义而言,这个功能更像是为了告诉使用该插件的开发者 “这个插件正在运行” 而存在的。

四、 代码实现

private class ReadThread extends Thread {    //在之前的设计中允许执行数次测试任务,直到手动停止    //因此急需要队列来排队未读取的日志    private Queue<MonkeyTask.Progress> queue = new ConcurrentLinkedQueue<>();    //任务执行标志    private volatile boolean running = true;    ReadThread offer(MonkeyTask.Progress progress) {        queue.offer(progress);        return this;    }    @Override    public final void run() {        while (running) {            MonkeyTask.Progress progress = queue.poll();            if (progress != null && FileUtil.canRead(progress.output)) {                try {//读取日志                    read(progress.process, progress.output);                } catch (Throwable throwable) {                    throwable.printStackTrace();                }            }            try {//每次读取日志完成后都休息一会~                Thread.sleep(SLEEP_TIME);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    private void read(Process process, File logfile) throws Throwable {        try (BufferedReader reader = IOUtil.open(logfile)) {            String line;            while (running) {                line = reader.readLine();                if (line != null) {                    //正常读出数据,回调方法以打印日志                    onLog(line);                } else if (process.isAlive()) {                    //磁盘读取速度通常总是高于写入速度的                    //reader没有读数据并不代表后续没有日志                    //让线程休眠一会等待新的日志写入                    try {                        Thread.sleep(SLEEP_TIME);                    } catch (Throwable e) {                        e.printStackTrace();                    }                } else {                    //没有日志,且线程结束,视为完成读取                    break;                }            }        }    }}
0 0
原创粉丝点击