Watch face: properly unloading large bitmaps?

Hello! New Connect IQ developer here — I'm working on a watch face which a) uses a full-screen bitmap as its background, and b) also provides a custom goal view containing its own bitmap. Having both bitmaps loaded at once causes an out-of-memory crash, so I'm trying to figure out how to properly load and unload these bitmaps dynamically.

My current technique works in the simulator, but crashes on my Vivoactive 3 with a generic "unhandled exception" error message. Specifically, I have an uninitialized <drawable class="WatchUi.Bitmap" /> in my watch face's layout, and during onShow(), I call (theBackgroundDrawable as Bitmap).setBitmap(Rez.Drawables.Background). Then, in onHide(), I call (theBackgroundDrawable as Bitmap).setBitmap(null).

Upon further investigation, the WatchUi.Bitmap docs do specify that "A null value passed for the bitmap parameter is only supported with ConnectIQ 5.0.0 and later." So, is unloading a bitmap simply unsupported in earlier CIQ versions? If not, what other techniques am I missing?

(Also, why does this work in the simulator, even on a pre-5.0.0 device? Shouldn't the simulator provide an appropriately versioned backend?)

  • It seems that the root cause of your problem is that you are using "WatchUi.Bitmap" as the custom class for your drawable. I don't think it's meant to be used that way (afaik) and even if it was, as you've found, it won't work so well for pre-CIQ 5 devices.

    Instead:

    - create your own class to draw the drawable, so you can fully control whether the bitmap in question is loaded or not (i.e. call loadResource() to load a bitmap, and invalidate all references to unload a bitmap). 

    or

    - alternatively, simply draw directly to the dc in onUpdate(), with functions such as drawBitmap()

    So, is unloading a bitmap simply unsupported in earlier CIQ versions?

    No, because if you want that kind of control, you instantiate WatchUi.Bitmap in developer-written code, not code which is auto-genned from resource xml (*). In the former code, if a dev called Bitmap.setBitmap() on a device which doesn't support passing in null, they could later simply invalidate all references to the Bitmap object itself, which should unload the bitmap resource  [**]

    [*] of course resource xml supports literal <bitmap> tags. But in that case, setBitmap would be unavailable anyway

    [**] here I am ignoring the nuances of the bitmap pool, which is available since CIQ 4, and is therefore unavailable for Vivoactive 3 (which is CIQ 3.3). The point is you can make the Bitmap object go away if you control the code that references the object (which you cannot do if you use Bitmap as a custom class for a drawable)

    (Also, why does this work in the simulator, even on a pre-5.0.0 device? Shouldn't the simulator provide an appropriately versioned backend?)

    There was a thread all about this last year, as a matter of fact. In that case, some API code/behaviour mean from newer devices was running on old devices (in the sim), which led someone to erroneously believe that a certain incorrect workaround would actually work, if only Garmin fixed a bug or two.

    Basically:

    1) unfortunately, the sim doesn't faithfully replicate old behaviour on old devices

    2) the docs for WatchUi.Bitmap / BitmapReference (iirc) don't really explain exactly which options are unavailable in older CIQ versions, which leads to problems 

  • All right, thanks, this makes sense. Also, I'd gotten too wrapped up in WatchUi.Bitmap, and forgot that dc.drawBitmap() existed; thanks for the pointer there Slight smile

    One follow-up question, as an offshoot of this — my new watchface onUpdate() function ends with:

    // Manually draw the background
    dc.drawBitmap(theBackgroundBitmap);
    // Call the parent onUpdate function to redraw the layout
    View.onUpdate(dc);

    I'd expect this to draw the background, then draw my layout elements on top; instead, it draws my layout with a black background. Is that a known artifact of View.onUpdate(), where it fills the screen with black as a first step or something?

  • Update: yes, apparently something in View.onUpdate() clears the screen before drawing the layout. I worked around this by manually looping through the layout and drawing each element individually. That works well enough for my purposes — thanks again for the clarification!

  • Update: yes, apparently something in View.onUpdate() clears the screen before drawing the layout. I worked around this by manually looping through the layout and drawing each element individually.

    The simpler, "standard" way of doing this is to call view.onUpdate(dc) *before* making any calls to draw directly to the dc (like dc.drawBitmap)

  • Ordinarily I would, but unfortunately this image is a background, so it has to be drawn before the rest of the layout. And, it works on real hardware, so I think I'm happy.

  • You can also skip using a layout and draw everything on the screen using dc calls

  • Ordinarily I would, but unfortunately this image is a background, so it has to be drawn before the rest of the layout

    Haha silly me, wasn't thinking. Glad it worked out for you!