📜 ⬆️ ⬇️

JAVA + Swing in 2013. Is it worth it?

On Habré Swing do not like. A search for “Swing” gives either neutral or negative references. Here are some of them:

I do not take the claim that Swing is ideal. It is not true. However, I will try to describe the pros and cons that I had to face.

Why swing


I have been working with Swing intermittently for a couple of years. Mostly in the evenings. I am writing a Visual Watermark program for group photo protection. I had a Java version in 2011. I wanted to make a Mac port and lick the interface, but I did not have any opportunity to write a separate program for each platform.

At the beginning of 2011, the UI libraries for cross-platform development were in this state:

At that time, Java was part of Mac OS X, had a great Native Look & Feel, and JRE for Windows weighed only 12 megabytes. I was naively confident of success. As a result, after 2 or 3 months of work, I ended up with the first version of the Java Swing program.
')
To date, the described problems have been fixed in QML and JavaFX. Therefore, if you are ready to work with the stage graph, then you should take them for a test drive.

Qt came under the wing of Digia. The release of the beta version for iPhone and Android gives hope for the further development of the library.

JavaFX became an open source library in February of this year. Its compatibility with OpenJDK is planned for JDK 9. When it comes out 9ka is unknown. Release 8th version is scheduled for early 2014.

Oh good


I'll start with the good. Suddenly you think that I also signed up for Swing-haters ?!

All hardware-accelerated rendering. Any Swing application is drawn on the GPU, nothing is required of the developer. This makes animation possible in the application. Including when the application is full-screen or deployed on a 24 "monitor.

MVC. Swing is criticized for being massive: each component consists of a view, a controller, and a model. At the same time, this makes it possible to quickly add the desired feature to an existing component. Everything is very flexible.

Java is managed code. You get rid of a heap of possible bugs that are “available” only for C ++ developers. The risk of Access Violation is minimized. Although this does not mean that you will not have other bugs. Memory leaks, for example.

Excellent development environment. Eclipse, Intellij IDEA, NetBeans - a huge choice. Everywhere there are refactorings, code formatting, auto-complete, support for unit tests,

A lot of libraries. LayoutManager, working with native objects, strings, web - just do not list. This is a huge trump card Java as a platform.

A lot of answers to questions. For example, here is the share of questions on StackOverflow for each of the UI libraries.



About every hundredth question on StackOverflow is a question about Swing. In practice, this means that most problems have already been solved. Most likely, you will not be left alone with a problem.

About bad


The previous part is like a sweet press release. Correct. This is what you may encounter.

Do not fix critical bugs. File.exists does not work since the release of JDK7 and there is no fix until now. Even if the bug is critical, you can wait for fixes for years.

The situation may become worse if you plan to use the native code. I was faced with a situation where the use of modal windows (for example, opening an OpenFileDialog) causes hangs on some computers. Given that Java Native Foundation is used according to examples in the documentation. And the bounty on StackOverflow didn't help me :-)

A bug with file.exists can be bypassed using classes from java.nio. This is a new API that was designed to solve performance problems with spreading folders.

What should be done:
  1. Run the application with the parameter –Dfile.encoding=UTF-8
  2. Instead of File.exists, use Files.exists(Paths.get(fileName))
  3. Instead of File.listFiles we use
     try (DirectoryStream<Path> ds = Files.newDirectoryStream(folder)) { for (Path file : ds) { // do something } } catch (IOException e) { e.printStackTrace(); } 

Or fix this bug yourself and push the bookmark through a series of reviews.

Swing - only hardware accelerated. This means your application will not work in VMware, Parallels or via remote desktop. If you are not ready to put up with it, then look towards SWT.

No 32-bit Mac builds. The official build is only 64 bits. Unfortunately, I do not know the reason for this decision. I can only guess what the matter is in some bugs.

For a while, Henri Gomez supported 32-bit and universal builds. Finished builds can be downloaded from his page on code.google.com. To my regret, the lack of time and the new work forced Henry to curtail this project. After saying goodbye, he put his build scripts on GitHub:
https://github.com/hgomez/obuildfactory

With their help, you can build OpenJDK for Mac and Linux. Great, but not quite. With these scripts, the 32-bit version for Mac is not going to. Inside the JDK is a huge amount of configuration files in which the build of a strictly 64-bit version for Mac is wired. Change the key in the main file and get an inoperable build. How Henri Gomez collected 32-bit builds is unknown to me.

Include the JRE in the distribution. Oracle executives opinion on application distribution: “The standalone self-contained installer with a bundled JRE for the target platform is a better application distribution model” ( source ). The most likely reason for this decision is a huge number of vulnerabilities in applets: Java has adopted the sieve banner from Flash.

This restriction is most strongly supported by Apple, which removed Java in Mac OS 10.7 Lion. They also forcibly disable it when installing new system updates.

JRE 7 weighs about 100 MB. The archive gets about 50. Unfortunately, the size of the JRE from update to update is growing and we have to solve the problem of the swollen distribution.

Not all BufferedImage objects use hardware acceleration. Only for BufferedImage.TYPE_INT_ *. Therefore, starting with JDK7, working with TYPE_4BYTE *, TYPE_3BYTE is inconsistent.

When accessing the data of the BufferedImage raster, the picture ceases to be drawn through the GPU. Why this is done is clear: the user changes the data, there is no “finished change” method and it is not clear when to re-fill them into video memory. At least this is logical.

In Visual Watermark, I used the C ++ library to load images and needed to turn the resulting pixels into a BufferedImage object. Changing one pixel one by one very slowly and I had to write directly to the raster buffer of the image. As soon as I called getData () on the raster, all my pictures stopped accelerating. Rummaging through the DataBufferInt code, I found a solution to this problem using reflection and wrote a small helper class:

 import java.awt.*; import java.awt.image.*; import java.lang.reflect.Field; import sun.awt.image.SunWritableRaster; import sun.java2d.StateTrackableDelegate; // Standard library prevents image acceleration once getData() method is called // This class provides a workaround to modify data quickly and still get hw-accel graphics public class AcceleratedImage { // Returns data object not preventing hardware image acceleration public static int[] getDataBuffer(DataBufferInt dataBuffer) { try { Field field = DataBufferInt.class.getDeclaredField("data"); field.setAccessible(true); int[] data = (int[])field.get(dataBuffer); return data; } catch (Exception e) { return null; } } // Marks the buffer dirty. You should call this method after changing the data buffer public static void markDirty(DataBufferInt dataBuffer) { try { Field field = DataBuffer.class.getDeclaredField("theTrackable"); field.setAccessible(true); StateTrackableDelegate theTrackable = (StateTrackableDelegate)field.get(dataBuffer); theTrackable.markDirty(); } catch (Exception e) { } } // Checks whether current image is in acceleratable state public static boolean isAcceleratableImage(BufferedImage img) { try { Field field = DataBuffer.class.getDeclaredField("theTrackable"); field.setAccessible(true); StateTrackableDelegate trackable = (StateTrackableDelegate)field.get(img.getRaster().getDataBuffer()); if (trackable.getState() == sun.java2d.StateTrackable.State.UNTRACKABLE) return false; field = SunWritableRaster.class.getDeclaredField("theTrackable"); field.setAccessible(true); trackable = (StateTrackableDelegate)field.get(img.getRaster()); return trackable.getState() != sun.java2d.StateTrackable.State.UNTRACKABLE; } catch(Exception e) { return false; } } public static BufferedImage convertToAcceleratedImage(Graphics _g, BufferedImage img) { if(!(_g instanceof Graphics2D)) return img; // We cannot obtain required information from Graphics object Graphics2D g = (Graphics2D)_g; GraphicsConfiguration gc = g.getDeviceConfiguration(); if (img.getColorModel().equals(gc.getColorModel()) && isAcceleratableImage(img)) return img; BufferedImage tmp = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency()); Graphics2D tmpGraphics = tmp.createGraphics(); tmpGraphics.drawImage(img, 0, 0, null); tmpGraphics.dispose(); img.flush(); return tmp; } } 


You need to use it like this:
 DataBufferInt dataBuffer = (DataBufferInt)bufferedImage.getRaster().getDataBuffer(); int[] data = AcceleratedImage.getDataBuffer(dataBuffer); //   AcceleratedImage.markDirty(dataBuffer); 

I did not check this code for images that have already been displayed.

There is no built-in animation and semi-transparency. The javax.swing.Timer object does two things:
  1. You can make animation components.
  2. Because of the simplicity of the class, doing it for a very long time.

There is a Timing Framework library that makes creating animations easier. Animation can be done like this:

 Animator viewAnimator = new Animator.Builder() .setDuration(duration, TimeUnit.MILLISECONDS) //    .setStartDirection(Direction.FORWARD) .setInterpolator(new AccelerationInterpolator(0.3, 0.7)) //     .setRepeatCount(1).addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { //   repaint(); } @Override public void end(Animator source) { //  -    } }).build(); viewAnimator.start(); 

The most commonly used animation is position and semi-transparency. If everything is OK with position control in Swing, standard components do not support semi-transparency. The problem is not the capabilities of the graphics engine, but that the components do not have the getAlpha / setAlpha property.

The Java application will not run in Mountain Lion due to GateKeeper. To solve this problem you need to sign up for Mac Developer for $ 99 / year. In return, Apple will give you a certificate to sign the code and the problem will go away.
You can sign a bundle with the application like this:
codesign –s “Developer ID” –f “path-to-my-app.app”

In total


In my opinion, the main disadvantage of Swing is uncertainty about the future of the platform, because critical bugs remain open. Only vulnerabilities in browser plugins are waiting for their fixes. One gets the feeling that the library was abandoned. All other problems are not so important.

And I will be very sad if this is true. Because writing a desktop application on Swing is fast, simple, and around a huge amount of ready-made and free code.

While I still have hope that developers in Oracle will have time to solve system problems.

UPDATE: I found out from comments that customization of controls in Qt is possible:

Thanks to silvansky
UPDATE: The user Skyggedans noticed that there was support for multi-threading in Air. It is done through additional SWF files with which you can customize communication.

Source: https://habr.com/ru/post/179459/


All Articles