JavaFX listview使用线程在自定义单元格中加载错误的图像

时间:2018-05-16 04:39:23

标签: java multithreading listview javafx

我在我的第一个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甚至单元格消失并且不再显示例如我在索引10中有单元格X而在索引25中有单元格Y如果我在Y单元格中快速滚动X显示的某些时间图像

如果我不清楚解释,因为它是我的第一个JavaFX项目

1 个答案:

答案 0 :(得分:1)

(?=X)

缓存图像是一个好主意,但这样做错了。你加载&amp;将图像插入到不同线程上的地图中。由于您不同步访问,因此无法保证两个线程都以相同的方式查看映射。另外考虑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的方式这一事实,并不是必须自己创建一个线程。
此外,对于大量数据,您可能希望摆脱GUI当前未使用的图像。使用Image s会是一个好主意 然而,主要问题是缺乏同步。如果您滚动得足够快,多个线程可能会为同一个单元格加载不同的图像,而您不知道,如果要启动的最后一个图像是最后一个执行SoftReference的图像。可以并行加载来自相同源的多个图像 也无法重新使用缓存。如果应用程序的其他部分需要图像,则无法以这种方式重复使用它们。

我的建议:

Platform.runLater