【发布时间】:2018-09-17 02:53:15
【问题描述】:
我有一个应用程序,其中有一个 TreeView,其中 TreeItems 将拥有 大量 叶 TreeItems。在树视图中拥有大量 TreeItem 会明显损害应用程序的性能,为避免这种情况,我将做的是,我一次只允许展开一个非叶 TreeItem,并且一旦折叠 TreeItem,我会清除它的子元素,并在需要时异步加载它们(当用户展开 TreeItem 时)。
奇怪的问题是,在下面的这个测试中,当我第一次单击树项上的展开箭头时,子项加载正常,如果我折叠它(这将清除子项)并再次展开它,有时它会起作用,其他program hogs and starts consuming 30% of the cpu for a couple of minutes 然后重新运行。 更奇怪的是如果我双击 TreeItem 来展开它(不使用箭头),即使在第一次程序启动时,猪也会立即启动。
我在这里可能做错了什么?
PS:
LazyTreeItem 类中的一些代码灵感来自 James_D 的回答 Here
我尝试在 fx 线程上运行 loadItems 任务(不使用 ItemLoader),但没有任何区别。
同时使用 JAVA 8 和 JAVA 9
会出现同样的问题
App.java
public class App extends Application {
private TreeView<Item> treeView = new TreeView<>();
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("TreeView Lazy Load");
primaryStage.setScene(new Scene(new StackPane(treeView), 300, 275));
initTreeView();
primaryStage.show();
}
private void initTreeView() {
treeView.setShowRoot(false);
treeView.setRoot(new TreeItem<>(null));
List<SingleItem> items = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
items.add(new SingleItem(String.valueOf(i)));
}
TreeItem<Item> parentItem = new TreeItem<>(new Item());
parentItem.getChildren().add(new LazyTreeItem(new MultipleItem(items)));
treeView.getRoot().getChildren().add(parentItem);
}
public static void main(String[] args) {
launch(args);
}
}
LazyTreeItem.java
public class LazyTreeItem extends TreeItem<Item> {
private boolean childrenLoaded = false;
private boolean isLoadingItems = false;
public LazyTreeItem(Item value) {
super(value);
// Unload data on folding to reduce memory
expandedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
flush();
}
});
}
@Override
public ObservableList<TreeItem<Item>> getChildren() {
if (childrenLoaded || !isExpanded()) {
return super.getChildren();
}
if (super.getChildren().size() == 0) {
// Filler node (will translate into loading icon in the
// TreeCell factory)
super.getChildren().add(new TreeItem<>(null));
}
if (getValue() instanceof MultipleItem) {
if (!isLoadingItems) {
loadItems();
}
}
return super.getChildren();
}
public void loadItems() {
Task<List<TreeItem<Item>>> task = new Task<List<TreeItem<Item>>>() {
@Override
protected List<TreeItem<Item>> call() {
isLoadingItems = true;
List<SingleItem> downloadSet = ((MultipleItem) LazyTreeItem.this.getValue()).getEntries();
List<TreeItem<Item>> treeNodes = new ArrayList<>();
for (SingleItem download : downloadSet) {
treeNodes.add(new TreeItem<>(download));
}
return treeNodes;
}
};
task.setOnSucceeded(e -> {
Platform.runLater(() -> {
super.getChildren().clear();
super.getChildren().addAll(task.getValue());
childrenLoaded = true;
isLoadingItems = false;
});
});
ItemLoader.getSingleton().load(task);
}
private void flush() {
childrenLoaded = false;
super.getChildren().clear();
}
@Override
public boolean isLeaf() {
if (childrenLoaded) {
return getChildren().isEmpty();
}
return false;
}
}
ItemLoader.java
public class ItemLoader implements Runnable {
private static ItemLoader instance;
private List<Task> queue = new ArrayList<>();
private Task prevTask = null;
private ItemLoader() {
Thread runner = new Thread(this);
runner.setName("ItemLoader thread");
runner.setDaemon(true);
runner.start();
}
public static ItemLoader getSingleton() {
if (instance == null) {
instance = new ItemLoader();
}
return instance;
}
public <T> void load(Task task) {
if (queue.size() < 1) {
queue.add(task);
}
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!queue.isEmpty()) {
Task task = queue.get(0);
if (task != prevTask) {
prevTask = task;
task.run();
queue.remove(task);
}
}
}
}
}
模型(Item.java、SingleItem.java、MultipleItem.java)
public class Item {
}
/****************************************************************
********** SingleItem ************
****************************************************************/
public class SingleItem extends Item {
private String id;
public SingleItem(String id) {
this.id = id;
}
public void setId(String id) {
this.id = id;
}
}
/****************************************************************
********** MultipleItem ************
****************************************************************/
public class MultipleItem extends Item {
private List<SingleItem> entries = new ArrayList<>();
public MultipleItem(List<SingleItem> entries) {
this.entries = entries;
}
public List<SingleItem> getEntries() {
return entries;
}
public void setEntries(List<SingleItem> entries) {
this.entries = entries;
}
}
【问题讨论】:
-
我可以在 fx 10 中重现该问题(双击该项目时) - 不是在三角形上展开时。不知道会发生什么,但是:leaf/leafProperty 没有同步(由于 api 错误,正如 James 指出的那样),所以一些内部组件 - 无论如何都很脏 - 可能会遇到更多麻烦。在你的鞋子里,我会进入一个分析轮:用分析器找到瓶颈,然后从那里开始。
-
okay .. 似乎与选择有关:如果未选择扩展项,则一切正常 - 如果选择它,它会挂起...
-
它挂在 TreeViewFocusModel 中,这在检查每个添加项目的行时有问题(与查询最后添加到块中的行相比,就像 TreeViewSelectionModel 那样) - 不知道如何破解,实现自定义 FocusModel 可能是唯一的出路(讨厌 .. 一切都非常隐藏且难以扩展)
-
绝对是一个错误(将很快提交),并且绝对(模缺失测试;)可以被自定义 FocusModel 破解,该模型仅在focusedIndex> sourceRow(c&p默认值-通过调整 treeItem 和 treeView 的内部状态是愚蠢的......这反过来又需要脏反射访问)然后比默认的 impl 做得更好,没有理由查询每个添加的项目的行,这样做是为了添加的最后一堆应该没问题
-
@kleopatra 如果/当您提交错误时,您应该包含
TreeTableViewFocusModel,因为它的作用几乎相同。