简体   繁体   中英

Using Javafx TimeLine In a Thread

I'm trying to create an internet checker class, that will check the connection to a certain url and update the status property accordingly. To avoid ui freeze i want to use a thread, and a timer to recheck after a certain interval. Problem is the CHECK method call in the timeline keyframe is called from the FX thread still. How can i use a timeline inside a thread?

CODE:

public class InternetChecker {
    private String baseUrl;
    /***Properties***/
    private ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.ACTIVE);

    /****************************************************************
     **********                  CONSTRUCTORS            ************
     ****************************************************************/
    public InternetChecker(String baseUrl) {
        this(baseUrl, 1000);
    }

    public InternetChecker(String baseUrl, int millisCheckInterval) {
        this.baseUrl = baseUrl;
        new Thread(() -> {
            Timeline timelineCheck = new Timeline();
            timelineCheck.getKeyFrames().add(
                    new KeyFrame(Duration.millis(millisCheckInterval), e -> {
                        check();
                    }));
            timelineCheck.setCycleCount(Animation.INDEFINITE);
            timelineCheck.play();
        }).start();
    }

    /*******************************
     * Will check if there is an internet connection present
     * and update the status accordingly
     *******************************/
    public void check() {
        // Check if base internet connection
        // is working, if it is we continue
        // to see if domain connection is working
        try {
            if ("127.0.0.1".equals(InetAddress.getLocalHost().getHostAddress())) {
                setStatus(Status.INTERNET_DISCONNECTED);
                return;
            }
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        // Check if base domain connection is working
        try {
            final URL url = new URL(baseUrl);
            final URLConnection conn = url.openConnection();
            conn.connect();
            conn.getInputStream().close();
            setStatus(Status.ACTIVE);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            setStatus(Status.BASE_URL_UNREACHABLE);
        }
    }

    /****************************************************************
     **********                  ACCESSORS               ************
     ****************************************************************/

    public Status getStatus() {
        return status.get();
    }

    public ObjectProperty<Status> statusProperty() {
        return status;
    }

    private void setStatus(Status status) {
        this.status.set(status);
    }

    /*******************************
     *  ACTIVE (Base url reachable)
     *  BASE_URL_UNREACHABLE (Internet available, but base url is unreachable)
     *  INTERNET_DISCONNECTED (Internet is not available)
     ********************************/
    public enum Status {
        ACTIVE,
        BASE_URL_UNREACHABLE,
        INTERNET_DISCONNECTED;
    }
}

Since you need to do a periodic background task that communicates with the JavaFX Application Thread it would be better to use a ScheduledService . This class executes a (new) Task periodically using an Executor that can be defined by the developer. Note that ScheduledService extends javafx.concurrent.Service .

Here is a skeleton example of what you'd need to do to implement this:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;

public class ConnectionStatusService extends ScheduledService<Status> {

    // Property allows you to change the "baseUrl" between executions
    private final StringProperty baseUrl = new SimpleStringProperty(this, "baseUrl");

    // property getter and setters omitted...

    @Override
    protected Task<Status> createTask() {
        // creates a new Task and gives the current "baseUrl"
        // as an argument. This is called every cycle
        return new ConnectionStatusTask(getBaseUrl());
    }

    private static class ConnectionStatusTask extends Task<Status> {

        // A Task is a one-shot thing and its initial state should be
        // immutable (or at least encapsulated from external modification).
        private final String baseUrl;

        private ConnectionStatusTask(String baseUrl) {
            this.baseUrl = baseUrl;
        }

        @Override
        protected Status call() throws Exception {
            // Do what you need to determine connection status
            return computedStatus;
        }
    }

}

Then you'd listen/bind to the lastValue property.

public void initService() {
    ConnectionStatusService service = new ConnectionStatusService();
    service.setBaseUrl(/* your URL */);
    service.setPeriod(Duration.seconds(1)); // run every 1 seconds
    service.lastValueProperty().addListener(/* your listener */); // or bind to this property

    // you may also want to add EventHandlers/Listeners to handle when the
    // service fails and such.
    service.start();
}

It's important you observe the lastValue property and not the value property. The reason is given in the Javadoc of lastValue :

The last successfully computed value. During each iteration, the "value" of the ScheduledService will be reset to null, as with any other Service. The "lastValue" however will be set to the most recently successfully computed value, even across iterations. It is reset however whenever you manually call reset or restart.

I recommend reading the Javadoc of Task , Service , and ScheduledService for more information. All three of these classes implement the javafx.concurrent.Worker interface.

You only want a single statement to be executed on the JavaFX application thread and that is status.set(status); . Since you're planing to run this statement with some delay in between, you can simply use Platform.runLater to do this.

As for repeatedly executing the check: ScheduledExecutorService is designed for this purpose.

public class InternetChecker implements Runnable {
    private final String baseUrl;
    /***Properties***/

    // use readonly wrapper here to restrict outside access to the property
    private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(Status.ACTIVE);

    /****************************************************************
     **********                  CONSTRUCTORS            ************
     ****************************************************************/
    public InternetChecker(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    /*******************************
     * Will check if there is an internet connection present
     * and update the status accordingly
     *******************************/
    @Override
    public void run() {
        // Check if base internet connection
        // is working, if it is we continue
        // to see if domain connection is working
        try {
            if ("127.0.0.1".equals(InetAddress.getLocalHost().getHostAddress())) {
                setStatus(Status.INTERNET_DISCONNECTED);
                return;
            }
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        // Check if base domain connection is working
        try {
            final URL url = new URL(baseUrl);
            final URLConnection conn = url.openConnection();
            conn.connect();
            conn.getInputStream().close();
            setStatus(Status.ACTIVE);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            setStatus(Status.BASE_URL_UNREACHABLE);
        }
    }

    /****************************************************************
     **********                  ACCESSORS               ************
     ****************************************************************/

    public Status getStatus() {
        return status.get();
    }

    public ReadOnlyObjectProperty<Status> statusProperty() {
        return status.getReadOnlyProperty​();
    }

    private void setStatus(final Status status) {
        Platform.runLater(() -> this.status.set(status));
    }

    /*******************************
     *  ACTIVE (Base url reachable)
     *  BASE_URL_UNREACHABLE (Internet available, but base url is unreachable)
     *  INTERNET_DISCONNECTED (Internet is not available)
     ********************************/
    public enum Status {
        ACTIVE,
        BASE_URL_UNREACHABLE,
        INTERNET_DISCONNECTED;
    }
}
InternetChecker checker = new InternetChecker(url);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor​();

// use delay here to avoid race condition
executorService.scheduleAtFixedDelay(checker, 0, millisCheckInterval, TimeUnit.MILLISECONDS);

Note that you need to shut down the service "manually" or use a ThreadFactory returning daemon threads:

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor​(r -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
});

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