How do I get a handle on a JavaFX application started using the following code?
CPUUsageChart.launch(CPUUsageChart.class);
CPUUsageChart extends Application from JavaFX and I am launching it from a main method of a simple Java project.
What I ultimately want to achieve is, that I can start the App and use its methods in the simple Java code, so that I do not have to do the calling in the Constructor of the Application extending class. I only want to use JavaFX's abilities for drawing charts and save them to HDD, for later usage, but I do not need to see any GUI made in JavaFX.
Proposed Solution
You can only launch an application once, so there will only ever be a single instance of your application class.
Because there is only a single instance of the application, you can store a reference to the instance in a static variable of the application when the application is started and you can get the instance as required from a static method (a kind of singleton pattern).
Caveats
Care must be taken to ensure:
Sample Solution
The sample code below uses a lock and a condition to ensure that the application instance is available before you try to use it. It will also require explicit shutdown of the JavaFX platform when it is no longer required.
Thanks to StackOverflow user James-D for some edit assistance with this code.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CPUUsageChart extends Application {
private static CPUUsageChart appInstance;
private static final Lock lock = new ReentrantLock();
private static final Condition appStarted = lock.newCondition();
/**
* Starts the application and records the instance.
* Sets the JavaFX platform not to exit implicitly.
* (e.g. an explicit call to Platform.exit() is required
* to exit the JavaFX Platform).
*/
@Override
public void start(Stage primaryStage) {
lock.lock();
try {
Platform.setImplicitExit(false);
appInstance = this;
appStarted.signalAll();
} finally {
lock.unlock();
}
}
/**
* Get an instance of the application.
* If the application has not already been launched it will be launched.
* This method will block the calling thread until the
* start method of the application has been invoked and the instance set.
* @return application instance (will not return null).
*/
public static CPUUsageChart getInstance() throws InterruptedException {
lock.lock();
try {
if (appInstance == null) {
Thread launchThread = new Thread(
() -> launch(CPUUsageChart.class),
"chart-launcher"
);
launchThread.setDaemon(true);
launchThread.start();
appStarted.await();
}
} finally {
lock.unlock();
}
return appInstance;
}
/**
* Public method which can be called to perform the main operation
* for this application.
* (render a chart and store the chart image to disk).
* This method can safely be called from any thread.
* Once this method is invoked, the data list should not be modified
* off of the JavaFX application thread.
*/
public void renderChart(
ObservableList<XYChart.Data<Number, Number>> data
) {
// ensure chart is rendered on the JavaFX application thread.
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> this.renderChartImpl(data));
} else {
this.renderChartImpl(data);
}
}
/**
* Private method which can be called to perform the main operation
* for this application.
* (render a chart and store the chart image to disk).
* This method must be invoked on the JavaFX application thread.
*/
private void renderChartImpl(
ObservableList<XYChart.Data<Number, Number>> data
) {
LineChart<Number, Number> chart = new LineChart<>(
new NumberAxis(),
new NumberAxis(0, 100, 10)
);
chart.setAnimated(false);
chart.getData().add(
new XYChart.Series<>("CPU Usage", data)
);
Scene scene = new Scene(chart);
try {
LocalDateTime now = LocalDateTime.now();
File file = Paths.get(
System.getProperty("user.dir"),
"cpu-usage-chart-" + now + ".png"
).toFile();
ImageIO.write(
SwingFXUtils.fromFXImage(
chart.snapshot(null, null),
null
),
"png",
file
);
System.out.println("Chart saved as: " + file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
To use this (from any thread):
try {
// get chartApp instance, blocking until it is available.
CPUUsageChart chartApp = CPUUsageChart.getInstance();
// call render chart as many times as you want
chartApp.renderChart(cpuUsageData);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// note your program should only ever exit the platform once.
Platform.exit();
}
Complete sample application which creates five graphs of cpu usage data with ten samples in each chart, each sample spaced by 100 milliseconds. As the sample invokes the chart application to render the charts, it will create chart png image files in the current java working directory and the file names will be output to the system console. No JavaFX stage or window is displayed.
Code to sample CPU usage copied from: How to get percentage of CPU usage of OS from java
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.XYChart;
import javax.management.*;
import java.lang.management.ManagementFactory;
public class ChartTest {
public static void main(String[] args) {
try {
CPUUsageChart chart = CPUUsageChart.getInstance();
for (int i = 0; i < 5; i++) {
ObservableList<XYChart.Data<Number, Number>> cpuUsageData = FXCollections.observableArrayList();
for (int j = 0; j < 10; j++) {
cpuUsageData.add(
new XYChart.Data<>(
j / 10.0,
getSystemCpuLoad()
)
);
Thread.sleep(100);
}
chart.renderChart(cpuUsageData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (MalformedObjectNameException | ReflectionException | InstanceNotFoundException e) {
e.printStackTrace();
} finally {
Platform.exit();
}
}
public static double getSystemCpuLoad() throws MalformedObjectNameException, ReflectionException, InstanceNotFoundException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = ObjectName.getInstance("java.lang:type=OperatingSystem");
AttributeList list = mbs.getAttributes(name, new String[]{ "SystemCpuLoad" });
if (list.isEmpty()) return Double.NaN;
Attribute att = (Attribute)list.get(0);
Double value = (Double)att.getValue();
if (value == -1.0) return Double.NaN; // usually takes a couple of seconds before we get real values
return ((int)(value * 1000) / 10.0); // returns a percentage value with 1 decimal point precision
}
}
Sample output (percentage CPU usage on the Y axis, and time in tenth of second sample spacing on the X axis).
Background Information
Alternate Implementations