Wednesday, April 16, 2014

Listening to Preferences

We'll keep this one quick. Before you spend a frustrating evening trying to figure out why sometimes your SharedPreferences.OnSharedPreferenceChangeListener is called and sometimes it isn't, and before you take up astrology due to a growing conviction that it is somehow related to the alignment of the stars, take note of the fact that SharedPreferences stores its listeners internally as weak references (with good reason; see addendum). No, they don't bother to document this fact. So if you do this:

PreferenceManager.getDefaultSharedPreferences(this)
    .registerOnSharedPreferenceChangeListener(
        new SharedPreferences.OnSharedPreferenceChangeListener()
        {
            @Override public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key)
            {
                // do important things
            }
        });

your anonymous listener immediately becomes a candidate for garbage collection. But you can't predict exactly when that will happen. Sometimes it will work (for a while), sometimes it won't. Do this instead:

mListener = new SharedPreferences.OnSharedPreferenceChangeListener()
{
    @Override public void onSharedPreferenceChanged(
        SharedPreferences sharedPreferences, String key)
    {
        // do important things
    }
};

PreferenceManager.getDefaultSharedPreferences(this)
    .registerOnSharedPreferenceChangeListener(mListener);

It's one more obnoxious member variable to keep around (or interface for your class to implement if you choose that route), but it's what you've gotta do.

Addendum


I think it actually makes good sense to use weak registration for observers, and that is in fact how I have typically implemented things in my own frameworks (in a perfect world, observers could choose whether they wanted to register strongly or weakly). The reason is that strong registration can easily lead to memory leaks when the observed is a persistent object and the observers are (meant to be!) transient. If SharedPreferences did in fact use strong observer registration and I did something like the first code snippet in an Activity, it would be a big problem--since the anonymous observer has an implicit reference to its outer class and there is no way to unregister it, the Activity object would be leaked and kept alive until whenever the SharedPreferences was destroyed. In my case the outer class is the Application object and I want that observer to stick around for as long as my app is running, but I imagine it's concerns like these that led to the choice of weak registration in the first place.

So: a sound decision, but since it differs from the decision made in (so far as I know) all other cases, they should have documented it. :)

No comments:

Post a Comment