JavaFX Pitfalls What no documentation tells you Speaker Andy - - PowerPoint PPT Presentation
JavaFX Pitfalls What no documentation tells you Speaker Andy - - PowerPoint PPT Presentation
JavaFX Pitfalls What no documentation tells you Speaker Andy Moncsek Senior Consultant Application Development @andyahcp Trivadis AG Switzerland - Zrich 600 employees in Switzerland, Germany and Austria consulting, development, BI,
Speaker
Andy Moncsek
Senior Consultant Application Development
Trivadis AG Switzerland - Zürich 600 employees in Switzerland, Germany and Austria consulting, development, BI, infrastructure, operation andy.moncsek@trivadis.com
@andyahcp
Pitfall - lost Listeners
Overall Performance
- Accidental Garbage Collection
Bindings. add(one, two). addListener((x, y, z) -> { if (z != null && z.intValue() == 10) { Alert alert = new Alert(INFORMATION); alert.showAndWait(); } });
lost Listeners
Same as:
- Accidental Garbage Collection
lost Listeners
DoubleBinding b = Bindings.add(one, two); b.addListener((x, y, z) -> { if (z != null && z.intValue() == 10) { Alert alert = new Alert(INFORMATION); alert.showAndWait(); } });
this.b = Bindings.add(one, two); this.b.addListener((x, y, z) -> { if (z != null && z.intValue() == 10) { Alert alert = new Alert(INFORMATION); alert.showAndWait(); } });
keep a reference of your binding
lost Listeners
better:
Solution?
Use a custom implementation for async things, if you can’t life with the pitfalls.
@RunWith(JfxRunner.class) public class TestServiceTest {
main thread
@Test public void testLongLastingOperation() { final TestService service = new TestService(); CompletableFuture<String> f = new CompletableFuture<>();
Platform.runLater(() -> {
service.valueProperty().addListener((b, o, n) -> { if (n != null) { f.complete(n); } }); service.restart(); }); assertEquals(„..“, future.get(5, TimeUnit.SECONDS));
} }
@RunWith(JfxRunner.class) public class TestServiceTest {
main thread FX thread
@Test public void testLongLastingOperation() { final TestService service = new TestService(); CompletableFuture<String> f = new CompletableFuture<>();
Platform.runLater(() -> {
service.valueProperty().addListener((b, o, n) -> { if (n != null) { f.complete(n); } }); service.restart(); }); assertEquals(„..“, future.get(5, TimeUnit.SECONDS));
} }
@RunWith(JfxRunner.class) public class TestServiceTest {
@Test public void testLongLastingOperation() { final TestService service = new TestService(); CompletableFuture<String> f = new CompletableFuture<>();
Platform.runLater(() -> {
service.valueProperty().addListener((b, o, n) -> { if (n != null) { f.complete(n); } }); service.restart(); }); assertEquals(„..“, f.get(5, TimeUnit.SECONDS));
} }
main thread FX thread
Conclusion
there are big problems testing services you can ignore it use own implementations
Don’t create a complex node graph in the UI Thread
Async Pitfalls
- not in FX - Thread? -> do not touch Nodes!
Threads, Tasks and Services
- take care of callbacks from other frameworks
(WebSocket, ...)
- create Nodes -> when not in SceneGraph OK
Threads, Tasks and Services
public class MyApp extends Application { @Override public void init() throws Exception { // Do some heavy lifting } @Override public void start(Stage primaryStage) throws Exception { ... primaryStage.show(); } public static void main(String[] args) { LauncherImpl.launchApplication(MyApp.class,Preloader.class, args); } }
Threads, Tasks and Services
public class MyApp extends Application { @Override public void init() throws Exception { // Do some heavy lifting } @Override public void start(Stage primaryStage) throws Exception { ... primaryStage.show(); } public static void main(String[] args) { LauncherImpl.launchApplication(MyApp.class,Preloader.class, args); } }
FX launcher thread FX thread
Threads, Tasks and Services
public class MyApp extends Application { private InitialUIService service = new InitialUIService(); @Override public void init() throws Exception { // Do some heavy lifting service.start(); } @Override public void start(Stage primaryStage) throws Exception { ... primaryStage.show(); } public static void main(String[] args) { LauncherImpl.launchApplication(MyApp.class,Preloader.class, args); } }
FX launcher thread FX thread Worker thread
Pixel Performance
- know the image size before loading
SimpleImageInfo info = new SimpleImageInfo(file); double width = info.getWidth(); double height = info.getHeight();
Uses meta-data in header [1] Do not load the whole image !
Image image = new Image("/50mp.jpg", true); double width = image.getWidth(); double height = image.getHeight();
Pushing - Pixels image size
Pushing - Pixels read/write
- PixelWriter
- Get pixel -> manipulate -> write pixel
- retrieving the pixel data from an Image
- writing the pixel data of a WritableImage
- PixelReader
Pushing Pixels read/write
PixelReader PixelWriter getArgb(x,y) setArgb(x,y,value) getColor(x,y) setColor(x,y,value) getPixels(…) setPixels(…)
VS.
Example: Crop an image Demo
Pushing Pixels PixelFormat
- Choose the right PixelFormat -> setPixels(…)
- INT_ARGB_PRE, INT_ARGB, BYTE_BGRA
new WriteableImage() -> QuantumToolkit
public PlatformImage createPlatformImage(int w, int h) { ByteBuffer bytebuf = ...; return com.sun.prism.Image.fromByteBgraPreData(bytebuf, w, h); }
Demo
Pushing Pixels PixelFormat
- Play video in JavaFX using VLC & setPixels(…)
- video fortmat!
- no MediaPlayer on ARM
Pushing - Pixels
- Rule 1: use viewer nodes (~20.000 Nodes )!
- Custom Controls —> x Nodes? complex CSS?
- picking, sync, compute dirty regions, apply CSS,….
- Use Canvas / Image when you have a lot to show!
Canvas: + higher fps
+ lower heap + (~128MB - 250 pic.) + Node count 2
Node: - lower fps
- higher heap
- (~200MB - 250 pic.)
- Node count >250
Pushing - Pixels
Conclusion
be aware of complex/many nodes Node or Images? —> depends choose correct PixelFormat & methods
Questions?
Links
- 1. https://jaimonmathew.wordpress.com/2011/01/29/
simpleimageinfo/
- 2. http://tomasmikula.github.io/blog/2015/02/10/
the-trouble-with-weak-listeners.html
- 3. https://blog.codecentric.de/en/2015/04/
tweaking-the-menu-bar-of-javafx-8-applications-
- n-os-x/
BACKUP
- JavaFX bindings uses WeakListeners to observe
dependencies
- dispose root binding should be enough
leaking Bindings
- Memory Leaks in Bindings
SimpleDoubleProperty prop = new SimpleDoubleProperty(0.0);
for (int i = 0; i < 1000000; ++i) { prop.add(5).multiply(10).dispose(); } prop.unbind(); // will it work?
leaking Bindings
NO! count WeakListener = 1000000 ! count WeakReference = 1000000 !
- Memory Leaks in Bindings
SimpleDoubleProperty prop = new SimpleDoubleProperty(0.0);
leaking Bindings
prop.add(10); // invalidates all ref. !! for (int i = 0; i < 1000000; ++i) { prop.add(5).multiply(10).dispose(); }
chain your service execution
FX-Thread Executor-Thread
- Solution 1 - Task & Platform.runLater
new Task<String>() { protected String call() throws Exception { Result r = service.get(); Platform.runLater(()-> .....); Result r2 = service2.get(); Platform.runLater(()-> .....); return "finished - step 2"; } };
chain your Service execution
FX-Thread Executor-Thread
- Platform.runLater - keeps order
- bad error handling & more !!
- Solution 2 - event handler
A.addEventHandler(SUCCEEDED, (s) -> { updateUI(); // start next service B.start(); }); A.addEventHandler(FAILED, (e) -> { doThat(); updateUI(); });
B.setOnSucceeded((state) -> { updateUI(); }); B.setOnFailed((event) -> { doThat(); updateUI(); });
chain your Service execution
FX-Thread Executor-Thread
- better error handling
- lots of code!
chain your Service execution
- Solution 3 - fluent API
handler .supplyAsync(()->longRunningTask1()) .onError(this::updateErrorMessage) .consumeOnFXThread(stringVal -> vbox.getChildren(). add(new Button(stringVal)) ) .supplyAsync(()->longRunningTask2()) .onError(this::updateErrorMessage)
.execute(this::updateUI); //non-blocking!
FX-Thread Executor-Thread
- good error handling
- easy to read
Test service with fluent API
final TestService service = new TestService();
IntStream.range(0, 5).forEach(v -> { handler.supplyOnFXThread(() -> { registerListenerAndStart(service, waitLatch); return service; }).functionOnExecutorThread((service) -> { // wait outside FX application thread !!! waitForService(waitLatch); return service; }).execute(serv -> { assertEquals("I'm an expensive result.", serv.getValue()); stop.countDown(); }); awaitServiceDone(stop); });
main thread FX thread
final TestService service = new TestService();
IntStream.range(0, 5).forEach(v -> { handler.supplyOnFXThread(() -> { registerListenerAndStart(service, waitLatch); return service; }).functionOnExecutorThread((service) -> { // wait outside FX application thread !!! awaitService(waitLatch); return service; }).execute(serv -> { assertEquals("I'm an expensive result.", serv.getValue()); stop.countDown(); }); // Non-blocking !!! awaitServiceResult(stop); });
main thread FX thread worker thread
OS X Menu Bar
OS X Menu Bar
- JavaFX is platform independent
- no direct access to OS X application menu
OS X Menu Bar
- Solution:
Eclipse SWT -> Cocoa native binding JavaFX wrapper: NSMenuFX [3] better