2014-11-03 2 views
0

Я делаю приложение, которое получает предупреждения.Обновление цвета строк TableView потребляет слишком много CPU

Предупреждение может иметь 4 возможных состояния:

  • Unresolved_New_0
  • Unresolved_New_1
  • Unresolved_Old
  • Решенный

Когда уведомление получено, оно находится в Unresolved_New_0 состоянии. В течение 10 секунд каждые 0,5 секунды состояние изменяется с Unresolved_New_0 на Unresolved_New_1 и наоборот. В зависимости от состояния I, установите другой цвет фона в строку таблицы (чтобы он вспыхнул на 10 секунд). При прохождении 10 секунд предупреждение переходит в состояние Unresolved_Old. Это приводит к тому, что цвет перестает меняться.

Чтобы реализовать это, у меня есть ScheduledThreadPoolExecutor, который я использую для представления реализации Runnable, которая в течение некоторого времени запускает runnable, используя Platform.runLater.

static class FxTask extends Runnable { 

    /** 
    * 
    * @param runnableDuring Runnable to be run while the task is active (run on the JavaFX application thread). 
    * @param runnableAfter Runnable to be run after the given duration is elapsed (run on the JavaFX application thread). 
    * @param duration Duration to run this task for. 
    * @param unit Time unit. 
    */ 
    public static FxTask create(final Runnable runnableDuring, final Runnable runnableAfter, final long duration, final TimeUnit unit) { 

     return new FxTask(runnableDuring, runnableAfter, duration, unit); 
    } 

    @Override 
    public void run() { 

     if (System.nanoTime() - mTimeStarted >= mTimeUnit.toNanos(mDuration)) 
     { 
      cancel(); 
      Platform.runLater(mRunnableAfter); 
     } 
     else 
      Platform.runLater(mRunnableDuring); 
    } 

    private FxTask(final Runnable during, final Runnable after, final long duration, final TimeUnit unit) { 

     mRunnableDuring = during; 
     mRunnableAfter = after; 

     mDuration = duration; 
     mTimeUnit = unit; 

     mTimeStarted = System.nanoTime(); 
    } 

    private final Runnable mRunnableDuring; 
    private final Runnable mRunnableAfter; 
    private final long mDuration; 
    private final TimeUnit mTimeUnit; 

    private final long mTimeStarted; 
} 

И планировать оповещения с помощью этого Runnable следующим образом:

final Alert alert = new Alert(...); 

scheduler.scheduleAtFixedRate(FxTask.create(
    () -> { 
     switch (alert.alertStateProperty().get()) { 

      case UNRESOLVED_NEW_0: 
       alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_1); 
       refreshTable(mAlertsTable); 
       break; 

      case UNRESOLVED_NEW_1: 
       alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_0); 
       refreshTable(mAlertsTable); 
       break; 
     } 
    }, 
    () -> { // This is run at the end 
     if (equalsAny(alert.alertStateProperty().get(), Alert.State.UNRESOLVED_NEW_0, SpreadAlert.State.UNRESOLVED_NEW_1)) { 
      alert.alertStateProperty().set(Alert.State.UNRESOLVED_OLD); 
      refreshTable(mAlertsTable); 
     } 
    }, 
    10, TimeUnit.SECONDS), 0, 500, TimeUnit.MILLISECONDS 
); 

Примечание: alertStateProperty() не показан на TableView (это не связано с какой-либо из ее столбцов). Итак, чтобы заставить JavaFx перерисовывать, мне нужно использовать refreshTable(), который, к сожалению, перерисовывает всю таблицу (?).

public static <T> void refreshTable(final TableView<T> table) { 

     table.getColumns().get(0).setVisible(false); 
     table.getColumns().get(0).setVisible(true); 
} 

Проблема заключается в том, что даже если я создаю небольшое количество предупреждений в то же время, использование процессора идет очень высоко: от 20% до 84%, иногда, в среднем около 40%. Когда 10s проходят для всех предупреждений, потребление процессора возвращается к 0%. Если я прокомментирую refreshTable(), CPU останется около 0%, что указывает на то, что это проблема.

Почему так много процессора используется? (Кстати, у меня есть 8 ядер). Есть ли другой способ перерисовать только одну строку без перерисовки всей таблицы?

Я даже попробовал «хакерский» метод - изменение всех значений предупреждений, а затем их возврат обратно, чтобы заставить JavaFx обнаруживать изменения и перерисовывать, но CPU снова был на тех же уровнях.

+0

Является ли 'Alert' тип элемента для' TableView'? Как вы на самом деле меняете цвет строки при изменении свойства (я вижу, что вы перерисовываете весь первый столбец, что вам не нужно делать, но когда вы показываете столбец, как он определяется цветом?) , –

ответ

1

Возможно, наиболее эффективным способом изменения цвета строки таблицы является использование фабрики табличных строк, создание таблицы, которая она создает, с соблюдением соответствующего свойства и обновление одного или более CSS PseudoClass состояний. Затем просто определите цвета во внешнем файле css.

Ниже приведена отдельная версия приложения, которое вы описали. Я просто использовал Timeline для выполнения «мигающих новых предупреждений», что меньше кода; но используйте исполнителя, как вы его, если хотите. Основная идея здесь - фабрика строк таблицы, а состояние псевдокласса, которое она манипулирует, наблюдая за свойством. В моей системе, если я заполню всю таблицу новыми (мигающими) строками, CPU не будет превышать около 35% (в процентах от одного ядра), что кажется вполне приемлемым.

Отметьте, что PseudoClass был представлен в Java 8.В более ранних версиях JavaFX вы можете добиться того же самого, манипулируя классами стилей, но вы должны быть осторожны, чтобы не дублировать классы стиля, поскольку они хранятся как List. Анекдотически подход псевдокласса более эффективен.

import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
import java.util.concurrent.atomic.AtomicInteger; 
import java.util.function.Function; 

import javafx.animation.KeyFrame; 
import javafx.animation.Timeline; 
import javafx.application.Application; 
import javafx.beans.binding.Bindings; 
import javafx.beans.property.IntegerProperty; 
import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.ReadOnlyObjectWrapper; 
import javafx.beans.property.SimpleIntegerProperty; 
import javafx.beans.property.SimpleObjectProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 
import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.collections.ListChangeListener.Change; 
import javafx.css.PseudoClass; 
import javafx.geometry.Insets; 
import javafx.geometry.Pos; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.ContentDisplay; 
import javafx.scene.control.TableCell; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableRow; 
import javafx.scene.control.TableView; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.HBox; 
import javafx.scene.layout.Region; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

public class AlertTableDemo extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     TableView<Alert> table = new TableView<>(); 
     table.getColumns().add(createColumn("Name", Alert::nameProperty)); 
     table.getColumns().add(createColumn("Value", Alert::valueProperty)); 
     TableColumn<Alert, Alert> resolveCol = 
       createColumn("Resolve", ReadOnlyObjectWrapper<Alert>::new); 
     resolveCol.setCellFactory(this::createResolveCell); 
     table.getColumns().add(resolveCol); 

     // just need a wrapper really, don't need the atomicity... 
     AtomicInteger alertCount = new AtomicInteger(); 
     Random rng = new Random(); 

     Button newAlertButton = new Button("New Alert"); 
     newAlertButton.setOnAction(event -> 
      table.getItems().add(new Alert("Alert "+alertCount.incrementAndGet(), 
        rng.nextInt(20)+1))); 

     // set psuedo-classes on table rows depending on alert state: 
     table.setRowFactory(tView -> { 

      TableRow<Alert> row = new TableRow<>(); 

      ChangeListener<Alert.State> listener = (obs, oldState, newState) -> 
       updateTableRowPseudoClassState(row, row.getItem().getState()); 

      row.itemProperty().addListener((obs, oldAlert, newAlert) -> { 
       if (oldAlert != null) { 
        oldAlert.stateProperty().removeListener(listener); 
       } 
       if (newAlert == null) { 
        clearTableRowPseudoClassState(row); 
       } else { 
        updateTableRowPseudoClassState(row, row.getItem().getState()); 
        newAlert.stateProperty().addListener(listener); 
       } 
      }); 

      return row ; 
     }); 

     // flash new alerts: 
     table.getItems().addListener((Change<? extends Alert> change) -> { 
      while (change.next()) { 
       if (change.wasAdded()) { 
        List<? extends Alert> newAlerts = 
          new ArrayList<>(change.getAddedSubList()); 
        flashAlerts(newAlerts); 
       } 
      } 
     }); 


     HBox controls = new HBox(5, newAlertButton); 
     controls.setPadding(new Insets(10)); 
     controls.setAlignment(Pos.CENTER); 

     BorderPane root = new BorderPane(table, null, null, controls, null); 
     Scene scene = new Scene(root, 800, 600); 
     scene.getStylesheets().add(
       getClass().getResource("alert-table.css").toExternalForm()); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

    private void flashAlerts(List<? extends Alert> newAlerts) { 
     Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5), 
       event -> { 
        for (Alert newAlert : newAlerts) { 
         if (newAlert.getState()==Alert.State.UNRESOLVED_NEW_0) { 
          newAlert.setState(Alert.State.UNRESOLVED_NEW_1); 
         } else if (newAlert.getState() == Alert.State.UNRESOLVED_NEW_1){ 
          newAlert.setState(Alert.State.UNRESOLVED_NEW_0); 
         } 
        } 
       })); 
     timeline.setOnFinished(event -> { 
      for (Alert newAlert : newAlerts) { 
       if (newAlert.getState() != Alert.State.RESOLVED) { 
        newAlert.setState(Alert.State.UNRESOLVED_OLD); 
       } 
      } 
     }); 
     timeline.setCycleCount(20); 
     timeline.play(); 
    } 

    private void clearTableRowPseudoClassState(Node node) { 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"), false); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"), false); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"), false); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"), false); 
    } 

    private void updateTableRowPseudoClassState(Node node, Alert.State state) { 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"), 
       state==Alert.State.UNRESOLVED_NEW_0); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"), 
       state==Alert.State.UNRESOLVED_NEW_1); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"), 
       state==Alert.State.UNRESOLVED_OLD); 
     node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"), 
       state==Alert.State.RESOLVED);  
    } 

    private TableCell<Alert, Alert> createResolveCell(TableColumn<Alert, Alert> col) { 
     TableCell<Alert, Alert> cell = new TableCell<>(); 
     Button resolveButton = new Button("Resolve"); 

     resolveButton.setOnAction(event -> 
      cell.getItem().setState(Alert.State.RESOLVED)); 

     cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 
     cell.setAlignment(Pos.CENTER); 
     cell.graphicProperty().bind(
       Bindings.when(cell.emptyProperty()) 
       .then((Node)null) 
       .otherwise(resolveButton)); 
     return cell ; 
    } 

    private <S, T> TableColumn<S, T> createColumn(String title, 
      Function<S, ObservableValue<T>> propertyMapper) { 
     TableColumn<S,T> col = new TableColumn<>(title); 
     col.setCellValueFactory(cellData -> propertyMapper.apply(cellData.getValue())); 
     col.setMinWidth(Region.USE_PREF_SIZE); 
     col.setPrefWidth(150); 
     return col ; 
    } 

    public static class Alert { 
     public enum State { 
      UNRESOLVED_NEW_0, UNRESOLVED_NEW_1, UNRESOLVED_OLD, RESOLVED 
     } 

     private final ObjectProperty<State> state = new SimpleObjectProperty<>(); 
     private final StringProperty name = new SimpleStringProperty(); 
     private final IntegerProperty value = new SimpleIntegerProperty(); 
     public final ObjectProperty<State> stateProperty() { 
      return this.state; 
     } 
     public final AlertTableDemo.Alert.State getState() { 
      return this.stateProperty().get(); 
     } 
     public final void setState(final AlertTableDemo.Alert.State state) { 
      this.stateProperty().set(state); 
     } 
     public final StringProperty nameProperty() { 
      return this.name; 
     } 
     public final java.lang.String getName() { 
      return this.nameProperty().get(); 
     } 
     public final void setName(final java.lang.String name) { 
      this.nameProperty().set(name); 
     } 
     public final IntegerProperty valueProperty() { 
      return this.value; 
     } 
     public final int getValue() { 
      return this.valueProperty().get(); 
     } 
     public final void setValue(final int value) { 
      this.valueProperty().set(value); 
     } 

     public Alert(String name, int value) { 
      setName(name); 
      setValue(value); 
      setState(State.UNRESOLVED_NEW_0); 
     } 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 

бдительный-table.css:

.table-row-cell:resolved { 
    -fx-background: green ; 
} 
.table-row-cell:unresolved-old { 
    -fx-background: red ; 
} 
.table-row-cell:unresolved-new { 
    -fx-background: blue ; 
} 
.table-row-cell:unresolved-new-alt { 
    -fx-background: yellow ; 
} 
Смежные вопросы