【问题标题】:URLImage in InfiniteScrollAdapter in Simulator shows NPE (CodenameOne)模拟器中 InfiniteScrollAdapter 中的 URLImage 显示 NPE (CodenameOne)
【发布时间】:2017-07-18 22:56:04
【问题描述】:

我的应用程序具有一个 InfiniteScrollAdapter,其中包含通过 URLImageURLImage.ImageAdapter 填充的图像。

在模拟器(Iphone3GS 或 Xoom 或 GoogleNexus7)中,NPE 会在第一次出现 InfiniteScrollAdapter 时显示,尽管该文件确实存在于服务器上。

请注意:在此测试中,数据库中只有一个条目。所以在下图中你应该看到的是同一行(图像 + 文本)重复了 3 次。

请注意,未显示图标中的顺序可能不同

我用来下载图片的代码是:

Image tempPlaceholder = Image.createImage(
            ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX,
            ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX,
            ParametresGeneraux.accentColor);
    Graphics gr = tempPlaceholder.getGraphics();
    gr.setAntiAliased(true);
    gr.setColor(ParametresGeneraux.accentColor);
    gr.fillArc(0, 0, ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX, ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX, 0, 360);

    EncodedImage roundPlaceholder = EncodedImage.createFromImage(tempPlaceholder, true);

    final Image reportImage = URLImage.createToStorage(
                            roundPlaceholder,
                            photoFilenameInStorage,
                            currentReport.getPhotoPath(),
                            ParametresGeneraux.RESIZE_SCALE_WITH_ROUND_MASK
                    );

这里是被覆盖的 imageAdapter 方法:

    public final static URLImage.ImageAdapter RESIZE_SCALE_WITH_ROUND_MASK = new URLImage.ImageAdapter() {
    @Override
    public EncodedImage adaptImage(EncodedImage downloadedImage, EncodedImage placeholderImage) {
        final Image[] tmp = new Image[1];

        if (!Display.getInstance().isEdt()) {
            // The image scaling has to be called from EDT
            Display.getInstance().callSeriallyAndWait(() -> {

                tmp[0] = downloadedImage.scaledLargerRatio(placeholderImage.getWidth(), placeholderImage.getHeight());
                if (tmp[0].getWidth() > placeholderImage.getWidth()) {
                    int diff = tmp[0].getWidth() - placeholderImage.getWidth();
                    int x = diff / 2;
                    tmp[0] = tmp[0].subImage(x, 0, placeholderImage.getWidth(), placeholderImage.getHeight(), true);
                } else if (tmp[0].getHeight() > placeholderImage.getHeight()) {
                    int diff = tmp[0].getHeight() - placeholderImage.getHeight();
                    int y = diff / 2;
                    tmp[0] = tmp[0].subImage(0, y, Math.min(placeholderImage.getWidth(), tmp[0].getWidth()),
                            Math.min(placeholderImage.getHeight(), tmp[0].getHeight()), true);
                }
            });
        } else {
            tmp[0] = downloadedImage.scaledLargerRatio(placeholderImage.getWidth(), placeholderImage.getHeight());
            if (tmp[0].getWidth() > placeholderImage.getWidth()) {
                int diff = tmp[0].getWidth() - placeholderImage.getWidth();
                int x = diff / 2;
                tmp[0] = tmp[0].subImage(x, 0, placeholderImage.getWidth(), placeholderImage.getHeight(), true);
            } else if (tmp[0].getHeight() > placeholderImage.getHeight()) {
                int diff = tmp[0].getHeight() - placeholderImage.getHeight();
                int y = diff / 2;
                tmp[0] = tmp[0].subImage(0, y, Math.min(placeholderImage.getWidth(), tmp[0].getWidth()),
                        Math.min(placeholderImage.getHeight(), tmp[0].getHeight()), true);
            }
        }

        EncodedImage[] image2Return = new EncodedImage[1];
        if (!Display.getInstance().isEdt()) {
            // The image scaling has to be called from EDT
            Display.getInstance().callSeriallyAndWait(() -> {
                Image roundMask = Image.createImage(tmp[0].getWidth(), tmp[0].getHeight(), 0xff000000);
                Graphics gr = roundMask.getGraphics();
                gr.setColor(0xffffff);

                gr.fillArc(0, 0, tmp[0].getWidth(), tmp[0].getHeight(), 0, 360);
                Object mask = roundMask.createMask();
                tmp[0] = tmp[0].applyMask(mask);
                image2Return[0] = EncodedImage.createFromImage(tmp[0], false);
            });
        } else {
            Image roundMask = Image.createImage(tmp[0].getWidth(), tmp[0].getHeight(), 0xff000000);
            Graphics gr = roundMask.getGraphics();
            gr.setColor(0xffffff);

            gr.fillArc(0, 0, tmp[0].getWidth(), tmp[0].getHeight(), 0, 360);
            Object mask = roundMask.createMask();
            tmp[0] = tmp[0].applyMask(mask);
            image2Return[0] = EncodedImage.createFromImage(tmp[0], false);
        }

        return image2Return[0];

    } 

在堆栈跟踪中,NPE 似乎源于被覆盖的URLImage.ImageAdapter

java.lang.IllegalArgumentException:为给定创建图像失败 长度的图像数据:0 在 com.codename1.ui.Image.createImage(Image.java:654) 在 com.codename1.ui.EncodedImage.getInternal(EncodedImage.java:365) 在 com.codename1.ui.EncodedImage.getInternalImpl(EncodedImage.java:340) 在 com.codename1.ui.EncodedImage.getHeight(EncodedImage.java:522) 在 com.codename1.ui.Image.scaledLargerRatio(Image.java:899) 在 com.my.application.ParametsGeneaux$1.lambda$adaptImage$0(ParametsGeneraux.java:564) 在 com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:95) 在 com.codename1.ui.Display.processSerialCalls(Display.java:1154) 在 com.codename1.ui.Display.edtLoopImpl(Display.java:1098) 在 com.codename1.ui.Display.invokeAndBlock(Display.java:1207) 在 com.codename1.ui.Display.invokeAndBlock(Display.java:1244) 在 com.codename1.ui.URLImage$DownloadCompleted.actionPerformed(URLImage.java:233) 在 com.codename1.ui.URLImage$4.onSucess(URLImage.java:301) 在 com.codename1.ui.URLImage$4.onSucess(URLImage.java:297) 在 com.codename1.util.CallbackDispatcher.run(CallbackDispatcher.java:53) 在 com.codename1.ui.Display.processSerialCalls(Display.java:1154) 在 com.codename1.ui.Display.edtLoopImpl(Display.java:1098) 在 com.codename1.ui.Display.mainEDTLoop(Display.java:999) 在 com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:120) 在 com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176) [EDT] 0:0:0,1 - 代号一修订: e5c43877074c18b4b5c7748d000e5cfac75ab749 2318

[EDT] 0:0:0,1 - 异常:java.lang.NullPointerException - null java.lang.NullPointerException 在 com.codename1.impl.javase.JavaSEPort.scale(JavaSEPort.java:3996) 在 com.codename1.ui.Image.scale(Image.java:1007) 在 com.codename1.ui.Image.scaledImpl(Image.java:953) 在 com.codename1.ui.Image.scaled(Image.java:918) 在 com.codename1.impl.javase.JavaSEPort$71.save(JavaSEPort.java:7659) 在 com.codename1.ui.EncodedImage.scaledEncoded(EncodedImage.java:626) 在 com.codename1.ui.EncodedImage.scaled(EncodedImage.java:653) 在 com.codename1.ui.Image.scaledLargerRatio(Image.java:904) 在 com.my.application.ParametsGeneaux$1.lambda$adaptImage$0(ParametsGeneraux.java:564) 在 com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:95) 在 com.codename1.ui.Display.processSerialCalls(Display.java:1154) 在 com.codename1.ui.Display.edtLoopImpl(Display.java:1098) 在 com.codename1.ui.Display.invokeAndBlock(Display.java:1207) 在 com.codename1.ui.Display.invokeAndBlock(Display.java:1244) 在 com.codename1.ui.URLImage$DownloadCompleted.actionPerformed(URLImage.java:233) 在 com.codename1.ui.URLImage$4.onSucess(URLImage.java:301) 在 com.codename1.ui.URLImage$4.onSucess(URLImage.java:297) 在 com.codename1.util.CallbackDispatcher.run(CallbackDispatcher.java:53) 在 com.codename1.ui.Display.processSerialCalls(Display.java:1154) 在 com.codename1.ui.Display.edtLoopImpl(Display.java:1098) 在 com.codename1.ui.Display.mainEDTLoop(Display.java:999) 在 com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:120) 在 com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176)

另外,扫一眼.cn1目录,可以看到后缀为“ImageURLTMP”的URLImage存储文件名,在没有NPE的情况下不会出现。

最后,如果我稍后返回此表单,一切都会按预期工作(显示图像,没有 NPE)。我尝试在 imageAdapter 中测试下载的图像是否为空,但 EncodedImage 不为空。

如何避免这种 NPE?

2017 年 3 月 1 日编辑

根据@Diamond 和@Shai 的回答,我相信NPE 的发生是因为InfiniteScrollAdapter 想要用行填充屏幕并因此同时启动同一图像的下载(因为它不在缓存中)。因此,一种解决方案可能是防止 InfiniteScrollAdapter 循环(因此它变得有限)。我怎样才能做到这一点 ?

另请注意,没有 404 错误,网络监视器显示响应代码 200,如下图所示。但是图像不应该被下载 3 次,不是吗?

【问题讨论】:

  • 发布您用于 InfinteScroller 和 URLImage 的代码的 sn-p
  • 我忘了!查看我的编辑@Diamond!

标签: java codenameone


【解决方案1】:

将您的 ImageAdapter 更改为以下内容:

public static final URLImage.ImageAdapter RESIZE_SCALE_WITH_ROUND_MASK = new URLImage.ImageAdapter() {
    @Override
    public EncodedImage adaptImage(EncodedImage downloadedImage, EncodedImage placeholderImage) {
        Image tmp = downloadedImage.scaledLargerRatio(placeholderImage.getWidth(), placeholderImage.getHeight());
        if (tmp.getWidth() > placeholderImage.getWidth()) {
            int diff = tmp.getWidth() - placeholderImage.getWidth();
            int x = diff / 2;
            tmp = tmp.subImage(x, 0, placeholderImage.getWidth(), placeholderImage.getHeight(), true);
        } else if (tmp.getHeight() > placeholderImage.getHeight()) {
            int diff = tmp.getHeight() - placeholderImage.getHeight();
            int y = diff / 2;
            tmp = tmp.subImage(0, y, Math.min(placeholderImage.getWidth(), tmp.getWidth()),
                    Math.min(placeholderImage.getHeight(), tmp.getHeight()), true);
        }
        Image roundMask = Image.createImage(tmp.getWidth(), tmp.getHeight(), 0xff000000);
        Graphics gr = roundMask.getGraphics();
        gr.setColor(0xffffff);
        gr.fillArc(0, 0, tmp.getWidth(), tmp.getHeight(), 0, 360);
        Object mask = roundMask.createMask();
        tmp = tmp.applyMask(mask);
        return EncodedImage.createFromImage(tmp, false);
    }

    @Override
    public boolean isAsyncAdapter() {
        return true;
    }
};

无需查看EDT

确保您的 tempPlaceholder 图像首先应用于您的组件,并在您的逻辑结束时,在 callSerially() 方法中调用您的 URLImage

Image tempPlaceholder = Image.createImage(
        ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX,
        ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX,
        ParametresGeneraux.accentColor);
Graphics gr = tempPlaceholder.getGraphics();
gr.setAntiAliased(true);
gr.setColor(ParametresGeneraux.accentColor);
gr.fillArc(0, 0, ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX, ParametresGeneraux.SIZE_OF_REPORT_PIC_IN_PX, 0, 360);

myComponent.setIcon(tempPlaceholder);


...


//Then call this at the end of everything
Display.getInstance().callSerially(() -> {
    EncodedImage roundPlaceholder = EncodedImage.createFromImage(tempPlaceholder, true);

    final Image reportImage = URLImage.createToStorage(
                        roundPlaceholder,
                        photoFilenameInStorage,
                        currentReport.getPhotoPath(),
                        ParametresGeneraux.RESIZE_SCALE_WITH_ROUND_MASK
                );
    myComponent.setIcon(reportImage);
    myComponent.getComponentForm().repaint();
});

编辑:

根据@Shai 的回答,您可以检查您当前是否正在下载相同的图像并防止另一个图像被拉取。因为这通常会导致冲突:

//Declare this at the top of your class
final static private Map<String, Image> LOADED_URLS = new HashMap<>(); 

//Then change the URLImage image method to this
Display.getInstance().callSerially(() -> {
    EncodedImage roundPlaceholder = EncodedImage.createFromImage(tempPlaceholder, true);

    final Image reportImage = LOADED_URLS.containsKey(photoFilenameInStorage) ? LOADED_URLS.get(photoFilenameInStorage)
                        : URLImage.createToStorage(
                        roundPlaceholder,
                        photoFilenameInStorage,
                        currentReport.getPhotoPath(),
                        ParametresGeneraux.RESIZE_SCALE_WITH_ROUND_MASK
                );
    LOADED_URLS.put(photoFilenameInStorage, reportImage);
    myComponent.setIcon(reportImage);
    myComponent.getComponentForm().repaint();
});

【讨论】:

  • 感谢@Diamond,检查正在进行的下载是有意义的。我试试看。
  • 您能解释一下为什么必须首先应用 'tempPlaceholder` 而不是 roundPlaceholder 吗?你是对的,从服务器中提取已经下载的文件会导致问题。事实上,我的经验是,只有在我的数据库中的条目数量不足时才会发生 NPE(实际上在纵向方向的 GoogleNexus7 上少于 3 个)。但即使你建议的LOADED_URLS NPE 仍然会发生。最好的办法是防止 InfiniteScrollAdapter 多次显示同一个元素。
  • 您可以使用两者中的任何一个。您是否通过网络监视器检查了队列中是否有任何图像返回 404 error
  • 谢谢@Diamond!关于您的问题,没有 404 错误(请参阅我的编辑),但相同的源文件被下载了 3 次(因为屏幕上适合 3 行),我相信这是导致 NPE 的原因,不是吗?
  • 编辑您的问题以添加您的所有代码,我可以尝试帮助您调试。记得删除敏感数据。
【解决方案2】:

在您的适配器中检查downloadedImage.getData() 是否为null。我认为它不是,它是 404 错误页面或类似的东西。

在这种情况下,您的适配器可以捕获异常并返回与您在不存在图像时期望看到的内容相匹配的回退。

这是第二次工作,因为系统看到 tmp 文件并假定正在下载,因此它不会再次调用下载代码。 tmp 文件稍后被重命名为最终的可下载文件。

【讨论】:

  • 谢谢@Shai。如我的问题所述,该文件确实存在于服务器上(无 404)!但我猜你的意思是“它不是 404”。我在哪里可以捕获 NPE,我在 URLImage 类中找不到错误方法覆盖?
  • 在 try-catch 块中包含 tmp[0] = downloadedImage.scaledLargerRatio(placeholderImage.getWidth(), placeholderImage.getHeight()); 确实会捕获 NPE。但是随后服务器上的原始图像将“永远”(至少在清除缓存之前)被显示。有没有办法防止InfiniteScrollAdapter 多次显示一行?我找不到任何FiniteScrollAdapter 类。
  • 如果您对图像的命名约定是严格的(例如使用公共前缀),您可以迭代存储的元素并检查您可以删除的 TMP 文件。您还可以通过删除旧图像来强制刷新。我们的一位客户在每次应用启动时都会自动执行此操作。
  • 好的,所以在使用 storageFilename 实例化 URLImage 之前,您建议删除所有存在的 TMP 文件,对吗?但是文件随后由 InfiniteSrollAdapter 下载,当行太少(图像也是如此)并且 InfiniteScrollAdapter“循环”通过图像以完全填满屏幕时会发生 NPE(如果您查看我的问题中的屏幕截图,圆形图像在服务器上具有所有相同的源图像)。我可以阻止 InfiniteScrollAdapter 滚动吗?否则我还能用什么?
  • 这不是人们所做的“建议”,而是要回答“刷新”问题而不是一般问题(这就是为什么最好将问题分开以避免混淆; -))
猜你喜欢
  • 2017-07-28
  • 1970-01-01
  • 2017-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-04
  • 2021-03-08
相关资源
最近更新 更多