读书笔记(Java极富客户端效果开发之一)

1.    main函数不要通过直接调用JFrame子类的构造来启动窗体程序,因为main本身并非运行于EDT中,因此可能会给UI带来同步问题,建议使用一下方式运行:

 1     public static void main(String args[]) {
2 Runnable doCreateAndShowGUI = new Runnable() {
3 @Override
4 public void run() {
5 //该方法为该类的私有静态方法,用于启动JFrame的主界面。
6 createAndShowGUI();
7 }
8 };
9 SwingUtilities.invokeLater(doCreateAndShowGUI);
10 }

2.    基于重载JComponent的paint属性来重绘该组件的所有区域,paint中的Graphics参数是自始至终存在并保持一致的,paintComponent中的Graphics参数则是在swing框架每次调用paintComponent函数之前新创建的。

 1     public void paint(Graphics g) {
2 // Create an image for the button graphics if necessary
3 if (buttonImage == null || buttonImage.getWidth() != getWidth() ||
4 buttonImage.getHeight() != getHeight()) {
5 //该函数来自Component,用于获取当前显示设备的metrics信息,
6         //然后根据该信息在创建设备兼容的BufferedImage对象。
7 buttonImage = getGraphicsConfiguration().createCompatibleImage(getWidth(), getHeight());
8 }
9 Graphics gButton = buttonImage.getGraphics();
10 gButton.setClip(g.getClip());
11
12 //使用超类中的paint方法将需要显示的image绘制到该内存图像中,不推荐直接从superclass绘制到g
13 //(子类paint方法的graphics对象),因为这样做可能会将superclass的graphics中的设备覆盖掉子
14 //类graphics中的设置,这样通过间接的内存图像可以避免该问题。
15 super.paint(gButton);
16
17 //这里必须直接目的graphics的composite属性,如果只是修改内存图像的composite,将只会是组件的
18 //内存被渲染成指定的alpha值,但是如果是设置的目的graphics的composite,那么整个组件内存图像的
19 //显示将被渲染成指定的透明度。
20 Graphics2D g2d = (Graphics2D)g;
21 AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f);
22 g2d.setComposite(newComposite);
23
24 // Copy the button's image to the destination graphics, translucently
25 g2d.drawImage(buttonImage, 0, 0, null);
26 }

3.    不要在EDT(Event Dispatch Thread)中执行较长的操作,从而避免UI被冻结的现象发生。
       和操作UI相关的code务必要放到EDT中调用,否则容易导致死锁。
       SwingUtilities.invodeLater(new Runnable()) 可以在EDT之外执行该方法,必将和UI操作相关的coding放到参数Runnable的实现中。该工具函数会自动将Runnable投递到EDT中执行。
       SwingUtilities.isEventDispatchThread() 如果当前的执行线程为EDT,该方法返回true,因此可以直接操作UI,否则表示EDT之外的线程,操作UI的界面不能直接在这里调用了。

 1     private void incrementLabel() {
2 tickCounter++;
3 Runnable code = new Runnable() {
4 public void run() {
5 counter.setText(String.valueOf(tickCounter));
6 }
7 }
8
9 if (SwingUtilities.isEventDispatchThread())
10 code.run()
11 else
12 SwingUtilities.invokeLater(code);
13 }

       SwingUtilities.invokeAndWait(), 该函数和invokeLater的主要区别就是该函数执行后将等待EDT线程执行该任务的完成,之后该函数才正常返回。
       非EDT线程可以通过调用repaint,强制EDT执行组件重绘。


4.    java.util.Timer 定时器中的TimerTask是在EDT之外的独立线程中执行的,因为不能直接执行UI的操作。
    java.swing.Timer 定时器中的任务是在EDT中执行的,因为可以包含直接操作UI的代码。
       
5.    打开抗锯齿:     

       g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
       以下的例子是根据OS桌面的设置,为文本的渲染打开抗锯齿。

 1     protected void paintComponent(Graphics g) {
2 Graphics2D g2d = (Graphics2D)g;
3 g2d.setColor(Color.WHITE);
4 g2d.fillRect(0, 0, getWidth(), getHeight());
5 g2d.setColor(Color.BLACK);
6 //该文本的渲染为非抗锯齿。
7 g2d.drawString("Unhinted string", 10, 20);
8
9 Toolkit tk = Toolkit.getDefaultToolkit();
10 desktopHints = (Map)(tk.getDesktopProperty("awt.font.desktophints"));
11 if (desktopHints != null) {
12 g2d.addRenderingHints(desktopHints);
13 }
14 g2d.drawString("Desktop-hinted string", 10, 40);
15 }

6.    图像缩放的提示:
       RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR //速度最快,效果最差
       RenderingHints.VALUE_INTERPOLATION_BILINEAR    //速度和效果适中
       RenderingHints.VALUE_INTERPOLATION_BICUBIC  //速度最慢,效果最好
       
7.    通过copyArea可以获取更好的性能
       copyArea(int x,int y,int width,int height,int dx,int dy);
       其中,x和y是需要被复制区域的左上角坐标,width和height分别表示该区域的宽度和高度,dx和dy表示相对于此区域的位置,如果为正值,则表示此区域的右边和下边,如果为负值,则表示此区域的左边和上边。

8.    通过逐次迭代的方式可以获得更好的效果,同时性能也非常不错,见下例:

 1     public BufferedImage getFasterScaledInstance(BufferedImage img,
2 int targetWidth, int targetHeight, Object hint,
3 boolean progressiveBilinear)
4 {
5 int type = (img.getTransparency() == Transparency.OPAQUE) ?
6 BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
7 BufferedImage ret = img;
8 BufferedImage scratchImage = null;
9 Graphics2D g2 = null;
10 int w, h;
11 int prevW = ret.getWidth();
12 int prevH = ret.getHeight();
13 boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
14
15 if (progressiveBilinear) {
16 // Use multi-step technique: start with original size, then
17 // scale down in multiple passes with drawImage()
18 // until the target size is reached
19 w = img.getWidth();
20 h = img.getHeight();
21 } else {
22 // Use one-step technique: scale directly from original
23 // size to target size with a single drawImage() call
24 w = targetWidth;
25 h = targetHeight;
26 }
27
28 do {
29 if (progressiveBilinear && w > targetWidth) {
30 w /= 2;
31 if (w < targetWidth)
32 w = targetWidth;
33 }
34
35 if (progressiveBilinear && h > targetHeight) {
36 h /= 2;
37 if (h < targetHeight)
38 h = targetHeight;
39 }
40
41 if (scratchImage == null || isTranslucent) {
42 // Use a single scratch buffer for all iterations
43 // and then copy to the final, correctly-sized image
44 // before returning
45 scratchImage = new BufferedImage(w, h, type);
46 g2 = scratchImage.createGraphics();
47 }
48 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
49 g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null);
50 prevW = w;
51 prevH = h;
52
53 ret = scratchImage;
54 } while (w != targetWidth || h != targetHeight);
55
56 if (g2 != null)
57 g2.dispose();
58
59 // If we used a scratch buffer that is larger than our target size,
60 // create an image of the right size and copy the results into it
61 if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) {
62 scratchImage = new BufferedImage(targetWidth, targetHeight, type);
63 g2 = scratchImage.createGraphics();
64 g2.drawImage(ret, 0, 0, null);
65 g2.dispose();
66 ret = scratchImage;
67 }
68 return ret;
69 }

9.    将一个普通的图像复制到一个显示设备兼容的图像中,以提高后期渲染操作的性能。
       注:由于显示设备兼容图像的图像数据存储方式和设备显示时的数据读取方式一致,不需要额外的转换,因此只是需要简单且高效的内存copy即可。

1     void drawCompatibleImage(BufferedImage suboptimalImage) {
2 GraphicsConfiguration gc = getConfiguration();
3 BufferedImage compatibleImage = gc.createCompatibleImage(
4 suboptimalImage.getWidth(),suboptimalImage.getHeight());
5 Graphics g = compatibleImage.getGraphics();
6 g.drawImage(suboptimalImage,0,0,null);
7 }

       制作一个自己的工具类便于创建设备兼容的图像。

 1     public class MineCompatible {
2 public static GraphicsConfiguration getConfiguration() {
3 return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
4 }
5
6 public static BufferedImage createCompatibleImage(BufferedImage image) {
7 return createCompatibleImage(image,image.getWidth(),image.getHeight());
8 }
9
10 public static BufferedImage createCompatibleImage(BufferedImage image,int width,int height) {
11 return getConfiguration().createCompatibleImage(width,height,image.getTransparency());
12 }
13
14 public static BufferedImage createCompatibleImage(int width,int height) {
15 return getConfiguration().createCompatibleImage(width,height);
16 }
17
18 public static BufferedImage createCompatibleTranslucentImage(int width,int height) {
19 return getConfiguration().createCompatibleImage(width,height,Transparency.TRANSLUCENT);
20 }
21
22 public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
23 BufferedImage image = ImageIO.read(resource);
24 return toCompatibleImage(image);
25 }
26
27 public static BufferedImage toCompatibleImage(BufferedImage image) {
28 GraphicsConfiguration gc = getConfiguration();
29 if (image.getColorModel().equals(gc.getColorModel())
30 return image;
31
32 BufferedImage compatibleImage = gc.createCompatibleImage(
33 image.getWidth(),image.getHeight(),image.getTransparency());
34 Graphics g = compatibleImage.getGraphics();
35 g.drawImage(image,0,0,null);
36 g.dispose();
37 return compatibleImage;
38 }
39 }

10.  托管图像:非托管的图像在设备显示时,是从system memory通过总线copy到VRAM的,托管图像将会在VRAM中创建一个system memory图像的副本,需要设备显示时,直接将VRAM中的副本copy到VRAM中用于显示,从而避免了通过总线将数据从system memory拷贝到VRAM了。
      抑制图像自动托管的两个因素:
      1) 通过Image的Raster,调用dataBuffer方法直接获取并且操作显示数据时,java 2d将自动关闭图像托管,由于此时是外部代码直接以数组的方式操作显示图像数据,因此java 2d无法监控显示数据是否已经被改变。注:一旦获取dataBuffer后,无法在通过将dataBuffer交还的方法重新使该图像成为托管图像。
      DataBuffer dataBuffer = image.getRaster().getDataBuffer();
      2) 频繁的渲染到图像,和1)不同,java 2d可以根据实际情况动态的处理是否需要将该图像设置为托管图像。
      由于渲染操作频繁,致使从system memory到与VRAM中的副本进行同步的操作变为额外的操作。

11. 通过保存中间图像的方式尽可能减少实时渲染的操作,在paintComponent中,通过将中间图像copy到swing后台缓冲,这样可以极大的提高显示效率,见下例:

 1     private void drawScaled(Graphics g) {
2 long startTime, endTime, totalTime;
3
4 // Scaled image
5 ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
6 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
7 startTime = System.nanoTime();
8 for (int i = 0; i < 100; ++i) {
9 g.drawImage(picture, SCALE_X, DIRECT_Y, scaleW, scaleH, null);
10 }
11 endTime = System.nanoTime();
12 totalTime = (endTime - startTime) / 1000000;
13 g.setColor(Color.BLACK);
14 g.drawString("Direct: " + ((float)totalTime/100) + " ms",
15 SCALE_X, DIRECT_Y + scaleH + 20);
16 System.out.println("scaled: " + totalTime);
17
18 // Intermediate Scaled
19 // First, create the intermediate image
20 if (scaledImage == null ||
21 scaledImage.getWidth() != scaleW ||
22 scaledImage.getHeight() != scaleH)
23 {
24 GraphicsConfiguration gc = getGraphicsConfiguration();
25 scaledImage = gc.createCompatibleImage(scaleW, scaleH);
26 Graphics gImg = scaledImage.getGraphics();
27 ((Graphics2D)gImg).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
28 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
29 gImg.drawImage(picture, 0, 0, scaleW, scaleH, null);
30 }
31 // Now, copy the intermediate image into place
32 startTime = System.nanoTime();
33 for (int i = 0; i < 100; ++i) {
34 g.drawImage(scaledImage, SCALE_X, INTERMEDIATE_Y, null);
35 }
36 endTime = System.nanoTime();
37 totalTime = (endTime - startTime) / 1000000;
38 g.drawString("Intermediate: " + ((float)totalTime/100) + " ms",
39 SCALE_X, INTERMEDIATE_Y + scaleH + 20);
40 System.out.println("Intermediate scaled: " + totalTime);
41 }

       需要实时渲染和计算的不规则图形也可以很好的利用中间图像,以避免更多的计算,但是对于不规则图像,需要考虑将中间图像的底色设置为透明,以便在copy的过程中只是复制这个图形的颜色,而不会复制这个图像的背景色,见下例(其中BITMASK为设置透明背景):

 1     private void renderSmiley(Graphics g, int x, int y) {
2 Graphics2D g2d = (Graphics2D)g.create();
3
4 // Yellow face
5 g2d.setColor(Color.yellow);
6 g2d.fillOval(x, y, SMILEY_SIZE, SMILEY_SIZE);
7
8 // Black eyes
9 g2d.setColor(Color.black);
10 g2d.fillOval(x + 30, y + 30, 8, 8);
11 g2d.fillOval(x + 62, y + 30, 8, 8);
12
13 // Black outline
14 g2d.drawOval(x, y, SMILEY_SIZE, SMILEY_SIZE);
15
16 // Black smile
17 g2d.setStroke(new BasicStroke(3.0f));
18 g2d.drawArc(x + 20, y + 20, 60, 60, 190, 160);
19
20 g2d.dispose();
21 }
22
23 /**
24 * Draws both the direct and intermediate-image versions of a
25 * smiley face, timing both variations.
26 */
27 private void drawSmiley(Graphics g) {
28 long startTime, endTime, totalTime;
29
30 // Draw smiley directly
31 startTime = System.nanoTime();
32 for (int i = 0; i < 100; ++i) {
33 renderSmiley(g, SMILEY_X, DIRECT_Y);
34 }
35 endTime = System.nanoTime();
36 totalTime = (endTime - startTime) / 1000000;
37 g.setColor(Color.BLACK);
38 g.drawString("Direct: " + ((float)totalTime/100) + " ms",
39 SMILEY_X, DIRECT_Y + SMILEY_SIZE + 20);
40 System.out.println("Direct: " + totalTime);
41
42 // Intermediate Smiley
43 // First, create the intermediate image if necessary
44 if (smileyImage == null) {
45 GraphicsConfiguration gc = getGraphicsConfiguration();
46 smileyImage = gc.createCompatibleImage(
47 SMILEY_SIZE + 1, SMILEY_SIZE + 1, Transparency.BITMASK);
48 Graphics2D gImg = (Graphics2D)smileyImage.getGraphics();
49 renderSmiley(gImg, 0, 0);
50 gImg.dispose();
51 }
52 // Now, copy the intermediate image
53 startTime = System.nanoTime();
54 for (int i = 0; i < 100; ++i) {
55 g.drawImage(smileyImage, SMILEY_X, INTERMEDIATE_Y, null);
56 }
57 endTime = System.nanoTime();
58 totalTime = (endTime - startTime) / 1000000;
59 g.drawString("Intermediate: " + ((float)totalTime/100) + " ms",
60 SMILEY_X, INTERMEDIATE_Y + SMILEY_SIZE + 20);
61 System.out.println("intermediate smiley: " + totalTime);
62 }

       如果中间图像使用半透明效果,不像以上两个例子分为使用了不透明和全透明背景,硬件加速将会被抑制。
  

原文地址:https://www.cnblogs.com/orangeform/p/2108322.html