WatchService 听起来像是一个令人兴奋的想法......不幸的是,它似乎与教程/api 中警告的一样低级,而且并不真正适合 Swing 事件模型(或者我遗漏了一些明显的东西,不是零概率
从教程中的 WatchDir 示例中获取代码(简化为仅处理单个目录),我基本上结束了
- 扩展 SwingWorker
- 在构造函数中做注册的东西
- 在 doInBackground 中放置等待键的无限循环
- 通过 key.pollEvents() 检索时发布每个 WatchEvent
通过使用已删除/创建的文件作为 newValue 触发 propertyChangeEvents 来处理块
@SuppressWarnings("unchecked") public class FileWorker extends SwingWorker<Void, WatchEvent<Path>> { public static final String DELETED = "deletedFile"; public static final String CREATED = "createdFile"; private Path directory; private WatchService watcher; public FileWorker(File file) throws IOException { directory = file.toPath(); watcher = FileSystems.getDefault().newWatchService(); directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); } @Override protected Void doInBackground() throws Exception { for (;;) { // wait for key to be signalled WatchKey key; try { key = watcher.take(); } catch (InterruptedException x) { return null; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); // TBD - provide example of how OVERFLOW event is handled if (kind == OVERFLOW) { continue; } publish((WatchEvent<Path>) event); } // reset key return if directory no longer accessible boolean valid = key.reset(); if (!valid) { break; } } return null; } @Override protected void process(List<WatchEvent<Path>> chunks) { super.process(chunks); for (WatchEvent<Path> event : chunks) { WatchEvent.Kind<?> kind = event.kind(); Path name = event.context(); Path child = directory.resolve(name); File file = child.toFile(); if (StandardWatchEventKinds.ENTRY_DELETE == kind) { firePropertyChange(DELETED, null, file); } else if (StandardWatchEventKinds.ENTRY_CREATE == kind) { firePropertyChange(CREATED, null, file); } } } }
基本思想是让使用代码幸福地不知道粘糊糊的细节:它监听属性更改并根据需要更新任意模型:
String testDir = "D:\\scans\\library";
File directory = new File(testDir);
final DefaultListModel<File> model = new DefaultListModel<File>();
for (File file : directory.listFiles()) {
model.addElement(file);
}
final FileWorker worker = new FileWorker(directory);
PropertyChangeListener l = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (FileWorker.DELETED == evt.getPropertyName()) {
model.removeElement(evt.getNewValue());
} else if (FileWorker.CREATED == evt.getPropertyName()) {
model.addElement((File) evt.getNewValue());
}
}
};
worker.addPropertyChangeListener(l);
JXList list = new JXList(model);
好像有效果,但是感觉不舒服
- 将我自己作为线程不可知论者:到目前为止,我看到的所有示例片段都使用 watcher.take() 阻塞了等待线程。他们为什么这样做?预计至少有些人会使用 watcher.poll() 并睡一会儿。
- SwingWorker 发布方法似乎不太适合:现在没关系,因为我只在看一个目录(不想在错误的方向上飞得太远 :) 尝试观看多个目录时(如原始 WatchDir 示例)有几个键和相对于其中之一的 WatchEvent。要解析路径,我需要事件和密钥正在监视的目录[A] - 但只能传递一个。不过,很可能逻辑的分布是错误的
[A]已编辑(由@trashgods 的评论触发) - 实际上这不是我必须与事件一起传递的密钥,而是它报告更改的目录。相应地改变了问题
仅供参考,这个问题是交叉发布到OTN swing 论坛
附录
阅读 WatchKey 的 api 文档:
如果有多个线程从监视服务检索信号键,则应注意确保仅在处理对象的事件后调用重置方法。
似乎暗示这些事件应该
- 在检索 WatchKey 的同一线程上进行处理
- 重置钥匙后不应触摸
不完全确定,但结合递归观看目录(多个)的(未来)要求,决定遵循@Eels 的建议,有点 - 很快就会发布我确定的代码
编辑 刚刚接受了我自己的答案 - 如果有人有合理的反对意见,我会谦虚地回复