JavaFX2 - 动态地将自定义(fxml)面板添加到gridpane时性能非常差

时间:2012-07-31 07:08:06

标签: performance javafx-2 fxml scenebuilder

问题 我想在运行时将通过javafx场景构建器构建的自定义面板添加到网格窗格中。我自定义的面板存在按钮,标签等。

我的尝试 我试图从窗格扩展......

public class Celli extends Pane{
    public Celli() throws IOException{
        Parent root = FXMLLoader.load(getClass().getResource("Cell.fxml"));
        this.getChildren().add(root);     
    }
}

...然后在控制器的添加方法中使用此面板

@FXML
private void textChange(KeyEvent event) {
    GridPane g = new GridPane();
        for (int i=0 : i<100; i++){
                g.getChildren().add(new Celli());
        }
    }
}

它有效,但表现非常差。

我在寻找什么 有没有办法通过javafx场景构建器设计面板(因此在fxml中有这个面板)然后在运行时将它添加到gridpane而不为每个实例使用这个fxmlloader。我认为它因fxml加载器而表现不佳。当我添加标准按钮时,例如whitout fxml它非常快。

3 个答案:

答案 0 :(得分:21)

简答:不,它不是(截至JavaFX 2.x和8.0)。它可能是未来版本(JFX> 8)

答案很长: FXMLLoader目前不是作为模板提供程序执行的,它一次又一次地实例化同一个项目。相反,它意味着是一个大型GUI(或序列化它们)的一次性加载器。

性能很差,因为根据FXML文件,每次调用load()时,FXMLLoader都必须通过反射查找类及其属性。这意味着:

  1. 对于每个import语句,尝试加载每个类,直到可以成功加载该类。
  2. 对于每个类,创建一个BeanAdapter,查找此类具有的所有属性,并尝试将给定参数应用于该属性。
  3. 将参数应用于属性再次通过反射完成。
  4. 对代码中完成的同一FXML文件的load()的后续调用目前也没有任何改进。这意味着:不缓存已找到的类,不缓存BeanAdapter等等。

    但是,通过将自定义类加载器设置为FXMLLoader实例,可以解决步骤1的性能问题:

    import java.io.IOException; 
    import java.net.URL; 
    import java.util.Enumeration; 
    import java.util.HashMap; 
    import java.util.Map; 
    
    public class MyClassLoader extends ClassLoader{ 
      private final Map<String, Class> classes = new HashMap<String, Class>(); 
      private final ClassLoader parent; 
    
      public MyClassLoader(ClassLoader parent) { 
        this.parent = parent; 
      } 
    
      @Override 
      public Class<?> loadClass(String name) throws ClassNotFoundException { 
        Class<?> c = findClass(name); 
        if ( c == null ) { 
          throw new ClassNotFoundException( name ); 
        } 
        return c; 
      } 
    
      @Override 
      protected Class<?> findClass( String className ) throws ClassNotFoundException { 
    // System.out.print("try to load " + className); 
        if (classes.containsKey(className)) { 
          Class<?> result = classes.get(className); 
          return result; 
        } else { 
          try { 
            Class<?> result = parent.loadClass(className); 
    // System.out.println(" -> success!"); 
            classes.put(className, result); 
            return result; 
          } catch (ClassNotFoundException ignore) { 
    // System.out.println(); 
            classes.put(className, null); 
            return null; 
          } 
        } 
      } 
    
      // ========= delegating methods ============= 
      @Override 
      public URL getResource( String name ) { 
        return parent.getResource(name); 
      } 
    
      @Override 
      public Enumeration<URL> getResources( String name ) throws IOException { 
        return parent.getResources(name); 
      } 
    
      @Override 
      public String toString() { 
        return parent.toString(); 
      } 
    
      @Override 
      public void setDefaultAssertionStatus(boolean enabled) { 
        parent.setDefaultAssertionStatus(enabled); 
      } 
    
      @Override 
      public void setPackageAssertionStatus(String packageName, boolean enabled) { 
        parent.setPackageAssertionStatus(packageName, enabled); 
      } 
    
      @Override 
      public void setClassAssertionStatus(String className, boolean enabled) { 
        parent.setClassAssertionStatus(className, enabled); 
      } 
    
      @Override 
      public void clearAssertionStatus() { 
        parent.clearAssertionStatus(); 
      } 
    }
    

    用法:

    public static ClassLoader cachingClassLoader = new MyClassLoader(FXMLLoader.getDefaultClassLoader()); 
    
    FXMLLoader loader = new FXMLLoader(resource); 
    loader.setClassLoader(cachingClassLoader); 
    

    这显着加快了性能。但是,步骤2没有解决方法,因此这可能仍然是一个问题。

    但是,官方JavaFX jira中已有功能请求。你很高兴支持这些要求。

    链接:

答案 1 :(得分:2)

我遇到过类似的问题。我还必须动态地多次加载一个基于fxml的自定义组件,这花费的时间太长了。在我的例子中,FXMLLoader.load方法调用很昂贵。

我的方法是并行化组件实例化,它解决了问题。

考虑到问题上发布的示例,使用多线程方法的控制器方法将是:

private void textChange(KeyEvent event) {
    GridPane g = new GridPane();
    // creates a thread pool with 10 threads
    ExecutorService threadPool = Executors.newFixedThreadPool(10); 
    final List<Celli> listOfComponents = Collections.synchronizedList(new ArrayList<Celli>(100));

    for (int i = 0; i < 100; i++) {
        // parallelizes component loading
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                listOfComponents.add(new Celli());
            }
        });
    }

    // waits until all threads completion
    try {
        threadPool.shutdown();      
        threadPool.awaitTermination(3, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        // seems to be a improbable exception, but we have to deal with it
        e.printStackTrace();
    }

    g.getChildren().addAll(listOfComponents);
}

答案 2 :(得分:0)

在@Sebastian先生的代码中添加“已经加载的类的缓存”代码。它对我有用。请建议更改以获得更好的性能。

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    System.out.println("In Class loader");

    Class result;
    System.out.println(" >>>>>> Load class : "+name);
    result = (Class)classes.get(name);
    if(result != null){
        System.out.println(" >>>>>> returning cached class.");
        return result;
    }else{
    Class<?> c = findClass(name);
    if ( c == null ) {
      throw new ClassNotFoundException( name );
    }
    System.out.println(" >>>>>> loading new class for first time only");
    return c;
    }
}