javafx pitfalls - trivadis-training.com · javafx pitfalls. speaker andy moncsek senior consultant...
TRANSCRIPT
What no documentation tells you…
JavaFX Pitfalls
Speaker
Andy Moncsek Senior Consultant Application Development
Trivadis AG Switzerland - Zürich 600 employees in Switzerland, Germany and Austria consulting, development, BI, infrastructure, operation [email protected]
@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 Servicespublic 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 Servicespublic 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 Servicespublic 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-on-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-ThreadExecutor-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-ThreadExecutor-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-ThreadExecutor-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-ThreadExecutor-Thread
• good error handling
• easy to read
TestservicewithfluentAPI
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
OSXMenuBar
OSXMenuBar
• JavaFX is platform independent
• no direct access to OS X application menu
OSXMenuBar
• Solution: Eclipse SWT -> Cocoa native binding
JavaFX wrapper: NSMenuFX [3]
better