JavaFX2 - очень низкая производительность при динамическом добавлении пользовательских (fxml) панелей в панель сетки.

Проблема Я хочу добавить специальные панели, созданные с помощью компоновщика сцен 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), а затем добавлять их в панель сетки во время выполнения без использования этого fxmlloader для каждый экземпляр. Я думаю, что он работает плохо из-за загрузчика fxml. Когда я добавляю стандартную кнопку, например. без fxml это намного быстрее.


person user1562969    schedule 31.07.2012    source источник


Ответы (3)


Короткий ответ: нет (начиная с JavaFX 2.x и 8.0). Это может быть в будущей версии (JFX > 8)

Подробный ответ: FXMLLoader в настоящее время не предназначен для работы в качестве поставщика шаблонов, который создает один и тот же элемент снова и снова. Скорее, он предназначен для одноразового загрузчика больших графических интерфейсов (или для их сериализации).

Производительность низкая, потому что в зависимости от файла FXML при каждом вызове load() FXMLLoader должен искать классы и их свойства посредством отражения. Это значит:

  1. Для каждого оператора импорта пытайтесь загрузить каждый класс до тех пор, пока класс не будет успешно загружен.
  2. Для каждого класса создайте BeanAdapter, который просматривает все свойства этого класса и пытается применить заданные параметры к свойству.
  3. Применение параметров к свойствам снова выполняется посредством отражения.

В настоящее время также нет улучшений для последующих вызовов load() к тому же файлу FXML, что и в коде. Это означает: никакого кеширования найденных классов, никакого кеширования BeanAdapters и так далее.

Однако существует обходной путь для производительности шага 1, путем установки пользовательского загрузчика классов для экземпляра FXMLLoader:

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 нет, так что это все еще может быть проблемой.

Однако для этого в официальной jira JavaFX уже есть запросы функций. Было бы неплохо с вашей стороны поддержать эту просьбу.

Ссылки:

person zhujik    schedule 31.07.2012
comment
Ссылки в конце уже недействительны. Кто-нибудь знает, на что они указывали, и может найти обновленный JDK-??? Идентификатор, соответствующий им? - person Itai; 05.02.2016
comment
@Itai, я обновил мертвые ссылки. Вы ищете идентификатор ошибки в новом средстве отслеживания ошибок по адресу https://bugs.openjdk.java.net/< /а>. См. ответ FibreFoX на Как найти ошибки в новом средстве отслеживания ошибок JavaFx: https://stackoverflow.com/questions/30646674/how-to-find-bugs-in-new-javafx-bug-tracker/33963599#33963599 - person XP1; 12.04.2018
comment
Мне удалось добиться некоторого повышения производительности с помощью aalto-xml, более быстрого XML на чистом Java. парсер, но такого рода изменения действительно глубоко копают, чтобы решить фундаментальную архитектурную проблему: я неправильно использую FXML. Я бы не рекомендовал использовать загрузчики FXML в качестве шаблонов. - person Groostav; 02.06.2020

У меня была аналогичная проблема. Мне также приходилось динамически загружать пользовательский компонент на основе 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);
}
person Crferreira    schedule 25.07.2013
comment
Какую версию JavaFx вы использовали 2.2 или 8.O? С JavaFX8.0 ваш метод завершается с ошибкой с исключением: java.lang.IllegalStateException: Not on FX application thread... - person Daniel; 19.11.2014

Просто добавьте код для «кеширования уже загруженных классов» в @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;
    }
}
person Suraj Dangat    schedule 20.01.2018