简体   繁体   中英

Handle on launched JavaFX Application

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:

  1. The instance is available before you try to use it.
  2. That threading rules are appropriately observed.
  3. That the JavaFX Platform is appropriately shutdown when it is no longer required.

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

  • You could use a JFXPanel rather than a class which extends Application. Though, then your application would also have a dependency on Swing.
  • You could make the main class of your application extend Application, so the application is automatically launched when your application is started rather than having a separate Application just for your usage chart.
  • If you have lots and lots of charts to render you could look a this off screen chart renderer implementation .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM