【问题标题】:JavaFX listview load a wrong image in the custom cell with a threadJavaFX listview 使用线程在自定义单元格中加载错误的图像
【发布时间】:2018-05-16 04:39:22
【问题描述】:

我从事我的第一个 JavaFX 项目,但遇到了 listview 自定义单元工厂的问题。这是我的代码

package ir.sadeghpro.instagram.cell;

import com.ibm.icu.util.PersianCalendar;
import ir.sadeghpro.insta.client.Comment;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;

import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class DischargeComment extends ListCell<Comment> {

    @FXML
    private AnchorPane pane;

    @FXML
    private TextFlow lblComment;

    @FXML
    private Label lblDate;

    @FXML
    private Label lblTime;

    @FXML
    private Hyperlink lblUsername;

    @FXML
    private ImageView img;


    public static String search = "";
    private FXMLLoader mLLoader;
    private static Map<String, Image> images = new HashMap<>();

    @Override
    protected void updateItem(Comment item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {

            setText(null);
            setGraphic(null);

        } else {
            if (mLLoader == null) {
                mLLoader = new FXMLLoader(getClass().getClassLoader().getResource("cell/discharge_comment.fxml"));
                mLLoader.setController(this);

                try {
                    mLLoader.load();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            ObservableList<Node> children = lblComment.getChildren();
            lblComment.setTextAlignment(TextAlignment.JUSTIFY);
            children.clear();
            if (!search.isEmpty() && item.getText().contains(search)) {
                int lastIndex = 0;
                for (int index = item.getText().indexOf(search); index >= 0; index = item.getText().indexOf(search, index + 1)) {
                    Text text = new Text(item.getText().substring(lastIndex, index));
                    text.setTextAlignment(TextAlignment.LEFT);
                    children.add(text);
                    text = new Text(item.getText().substring(index, index + search.length()));
                    text.setTextAlignment(TextAlignment.LEFT);
                    text.setFill(Color.RED);
                    children.add(text);
                    lastIndex = index + search.length();
                }
                if (lastIndex < item.getText().length()) {
                    Text text = new Text(item.getText().substring(lastIndex));
                    text.setTextAlignment(TextAlignment.LEFT);
                    children.add(text);
                }
            } else {
                children.add(new Text(item.getText()));
            }
            PersianCalendar persianCalendar = new PersianCalendar();
            persianCalendar.setTimeInMillis(item.getTimestamp() * 1000L);
            lblDate.setText(persianCalendar.get(Calendar.YEAR) + "/" + (persianCalendar.get(Calendar.MONTH) + 1) + "/" + persianCalendar.get(Calendar.DAY_OF_MONTH));
            lblTime.setText(persianCalendar.get(Calendar.HOUR) + ":" + persianCalendar.get(Calendar.MINUTE));
            lblUsername.setText(item.getOwnerUsername());
            Image image;
            if ((image = images.get(item.getOwnerId())) == null) {
                img.setImage(null);
                new Thread(() -> {
                    Image image1 = new Image(item.getOwnerProfilePicUrl());
                    images.put(item.getOwnerId(), image1);
                    Platform.runLater(() -> img.setImage(image1));
                }).start();
            } else {
                img.setImage(image);
            }
            Circle clip = new Circle(25, 25, 25);
            img.setClip(clip);
            lblUsername.setOnMouseClicked(e->{
                try {
                    Desktop.getDesktop().browse(new URI("https://www.instagram.com/" + item.getOwnerUsername()));
                } catch (IOException | URISyntaxException exception) {
                    exception.printStackTrace();
                }
            });

            setText(null);
            setGraphic(pane);
            setHeight(Region.USE_COMPUTED_SIZE);
        }
    }
}

我的问题在第 107-114 行。在这一行中,如果用户的图像在我下载它并添加到 hashmap 图像之前没有下载,接下来将它添加到查看,它工作正常但是当滚动列表快速时可能会下载 100 个图像,因为在下载图像后的线程中添加到单元格 ImageView 甚至单元格消失并且不再显示,例如,如果我在 Y 单元格中快速滚动一段时间,则 X 的图像在索引 10 中显示,而在索引 25 中的单元格 Y 显示在 Y 单元格中

对不起,如果我没有解释清楚,因为这是我的第一个 JavaFX 项目

【问题讨论】:

    标签: java multithreading listview javafx


    【解决方案1】:
    private static Map<String, Image> images = new HashMap<>();
    
    ...
    
    Image image;
    if ((image = images.get(item.getOwnerId())) == null) {
        img.setImage(null);
        new Thread(() -> {
            Image image1 = new Image(item.getOwnerProfilePicUrl());
            images.put(item.getOwnerId(), image1);
            Platform.runLater(() -> img.setImage(image1));
        }).start();
    } else {
        img.setImage(image);
    }
    

    缓存图像是个好主意,但这样做是错误的。您在不同的线程上加载并插入图像到地图。由于您不同步访问,因此无法保证两个线程以相同的方式查看地图。还考虑到Image 提供了一种异步加载Image 的方式这一事实,实际上并不需要自己创建线程。
    此外,对于大量数据,您可能希望删除 GUI 当前未使用的图像。使用SoftReferences 是个好主意。
    然而,主要问题是缺乏同步。如果你滚动得足够快,多个线程可能会为同一个单元格加载不同的图像,而你不知道最后一个要启动的是否是最后一个要执行的Platform.runLater。可以并行加载来自同一来源的多个图像。
    也没有办法重用你的缓存。如果应用程序的其他部分需要图像,则无法通过这种方式重复使用它们。

    我的建议:

    ...
    
    import java.awt.Desktop; // importing more classes from awt than neccessary could result in problems
    ...
    
    public class DischargeComment extends ListCell<Comment> {
    
        ...
    
        /**
         * Constructor to pass external cache
         * @param cache 
         */
        public DischargeComment(Map<String, SoftReference<Image>> cache) {
            if (cache == null) {
                throw new IllegalArgumentException();
            }
            this.cache = cache;
        }
    
        /**
         * constructor using the default cache
         */
        public DischargeComment() {
            this(getDefaultCache());
        }
    
        private final Map<String, SoftReference<Image>> cache;
    
        private static Map<String, SoftReference<Image>> defaultCache;
    
        private static final URL FXML_URL = DischargeComment.class.getResource("cell/discharge_comment.fxml");
    
        public static Map<String, SoftReference<Image>> getDefaultCache() {
            if (defaultCache == null) {
                defaultCache = new HashMap<>();
            }
            return defaultCache;
        }
    
        public static String search = "";
        private boolean loaded = false; // no need for a reference to fxmlloader here
    
        @Override
        protected void updateItem(Comment item, boolean empty) {
            super.updateItem(item, empty);
    
            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                if (!loaded) {
                    FXMLLoader mLLoader = new FXMLLoader(FXML_URL);
                    mLLoader.setController(this);
    
                    try {
                        mLLoader.load();
                        img.setClip(new Circle(25, 25, 25));
                        loaded = true;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
    
                ...
    
                // use single access here
                // also use url as key
                cache.compute(item.getOwnerProfilePicUrl(), (key, value) -> {
                    Image image = null;
                    if (value != null) {
                        image = value.get();
                    }
                    if (image == null) {
                        image = new Image(key, true); // load image in background
                        value = new SoftReference<>(image);
                    }
                    img.setImage(image);
                    return value;
                });
    
                lblUsername.setOnMouseClicked(e->{
                    try {
                        Desktop.getDesktop().browse(new URI("https://www.instagram.com/" + item.getOwnerUsername()));
                    } catch (IOException | URISyntaxException exception) {
                        exception.printStackTrace();
                    }
                });
    
                setText(null);
                setGraphic(pane);
                setPrefHeight(Region.USE_COMPUTED_SIZE); // don't set height to -1
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-02-22
      • 1970-01-01
      • 2013-11-27
      • 2015-02-09
      • 2016-07-23
      • 2017-01-14
      • 1970-01-01
      • 1970-01-01
      • 2016-10-23
      相关资源
      最近更新 更多