Why do my images sometimes reload (and WriteableBi

2019-03-31 03:08发布

问题:

When I resume my app, sometimes it loses its images. I can reproduce this even in trivial apps.

This manifests in two different ways, depending on where the image came from:

  • If I did something like <Image Source="/Assets/Logo.png"/> (i.e., loading an image from my app package by URI), the image will disappear briefly, and then reload. This is easier to see when the screen contains many images; they reload in sequence, so I can watch the image loading ripple across the screen.
  • If I created a WriteableBitmap and bound it to Image.Source, then the bitmap disappears entirely, never to return.

I could probably live with this if it was just reloading images (though the ripple effect is unpleasant), but I do quite a bit of dynamic image generation, and it's not cool for all my WriteableBitmaps to disappear.

This doesn't happen every time the app suspends and resumes, only sometimes. I haven't nailed down a reliable repro case, but it seems that the longer it's suspended, and the more other things I'm doing in the interim (surfing the web in desktop Chrome, playing fullscreen DirectX games like TapTiles and Microsoft Minesweeper), the more likely it is that, when I switch back to my app, the images will be lost. (The app is not getting terminated and re-launched -- it's still the same app instance.)

I wonder if it's something to do with images getting flushed from video memory. But I have no idea how to test this hypothesis, so at this point it's just a guess.

So far, I haven't found any way for my program to even tell that WriteableBitmaps are being lost. The sequence of events is identical to a normal suspend and resume:

  • Window.Current.Activated fires with args.WindowActivationState == Deactivated
  • Window.Current.VisibilityChanged fires with args.Visible == false
  • Application.Suspending fires
  • (time passes)
  • Application.Resuming fires
  • Window.Current.VisibilityChanged fires with args.Visible == true
  • Window.Current.Activated fires with args.WindowActivationState == CodeActivated

I haven't had any luck finding an "and by the way, all your images went away" event. WriteableBitmap has no events. Image has ImageOpened and ImageFailed events, but those only fire when I load an image from a URI -- they never fire when Source is a WriteableBitmap.

I've been able to reproduce this in trivial apps, so it's not caused by some third-party library. A blank page with an Image loaded by URI will fire ImageOpened again (and the image will flicker briefly before it finishes reloading) if the app has been suspended for a while and then you resume it. A blank page with an image initialized to a WriteableBitmap will disappear if the app has been suspended for a while and then you resume it.

How can I fix the loss of WriteableBitmaps (and, if possible, content-URI images too)? Is there any way that I can prevent the images from getting lost in the first place? If not, is there any way I can detect that they've been lost, so I can re-create my WriteableBitmaps?

(For bonus points, I'd love to know why the images disappear, but that part is entirely optional. I've long since given up on expecting WinRT to make sense.)

回答1:

I've done some experiments that made it clear that WinRT is just losing the contents of the WriteableBitmap, not the WriteableBitmap itself. The WriteableBitmap instance is still viable; if you give it new pixel data, it works fine. It's just that sometimes, on app resume, its contents get replaced with a swath of transparent pixels.

I've been able to work around this problem by hooking my app's OnResuming event, and using that event to grab my WriteableBitmap's PixelBuffer.AsStream(), write new pixels, and call WriteableBitmap.Invalidate(). If the code that regenerates the image is synchronous, then the image doesn't even flicker when the app resumes.

I suspect that ordinary bitmaps (loaded via a Source URI) are also losing their pixel buffers, and having to reload their pixels after the app resumes. Image loading is async, which explains the delay / flicker before their contents reappear.

I haven't found a way to know whether the image lost its pixels (though obviously URI-loaded images have some way of knowing, since they reload themselves automatically). So with WriteableBitmaps, the safest thing seems to be to always repopulate them on app resume.



回答2:

To somewhat supplant the previous answer, I've found that simply invalidating the WriteableBitmap should do the trick. I would advocate trying that first before you rewrite the image, as that may have a more substantial impact on your application over just Invalidating it.

This bug can also be reproduced by forcing your application to suspend and resume. It initially was very difficult for us to pin down when it would happen as we were waiting for the application to suspend. This is a link to an article on how to simulate Suspend and Resume in WinRT applications.