Proper notification of AsyncTaskLoader about data

2020-06-19 10:23发布

问题:

I want to implement AsyncTaskLoader for my custom data source:

public class DataSource {
    public interface DataSourceObserver {
        void onDataChanged();
    }
    ...
}

DataSource will keep list of registered observers and will notify them about changes. CustomLoader will implement DataSourceObserver. The question is how to properly notify CustomLoader since Loader.onContentChanged() must be called from UI thread but in my case DataSource operations (and calls to DataSourceObserver.onDataChanged()) will be done from background threads.

Updated with idea from Selvin tip:

public class CustomLoader extends AsyncTaskLoader<...> implements DataSource.DataSourceObserver {
    private final Handler observerHandler;

    public CustomLoader(Context context) {
        super(context);
        observerHandler = new Handler()
    }

    @Override
    public void onDataChanged() {
        observerHandler.post(new Runnable() {
            @Override
            public void run() {
                onContentChanged();
            }
        });
    }
}

回答1:

I've had a lot of success using Local Broadcasts in a case that's very similar to yours. The method involves an AsyncTaskLoader implementation that will register a BroadcastReceiver listening for a particular String that describes what's changed. This BroadcastReceiver keeps a reference to the Loader and calls onContentChanged. When the data needs a refresh, make the Local Broadcast with the aforementioned String and the BroadcastReceiver will hear it and trigger the load. Here's some example code, it may not work perfectly if you drop it in, I've generalized some class names, but hopefully you'll get the idea:

Broadcast Receiver to be used in your Loader Implmentation:

public class LoaderBroadcastReceiver extends BroadcastReceiver
{
    private Loader loader;

    public LoaderBroadcastReceiver(Loader loader)
    {
        this.loader = loader;
    }

    @Override
    public void onReceive(Context context, Intent intent)
    {
        loader.onContentChanged();
    } 
}

Loader Implementation registers the Receiver in onStartLoading()

private LoaderBroadcastReceiver loaderBroadcastReceiver = null;

@Override
protected void onStartLoading()
{
     //... some code here

    if(loaderBroadcastReceiver == null)
    {
        loaderBroadcastReceiver = new LoaderBroadcastReceiver(this);
        LocalBroadcastManager.getInstance(getContext()).registerReceiver(loaderBroadcastReceiver, new IntentFilter("NEWDATASTRING"));
    }

    //... some more code here
}

Finally, here's how onDataChanged in DataSource will make the Broadcast. It'll need a Context to help send the Broadcast. Since this can be called from an arbitrary Thread, I'd use your ApplicationContext, since an Context from an Activity could cause problems if the Activity is destroyed.

public class DataSource 
{
    public interface DataSourceObserver 
    {
        void onDataChanged(Context applicationContext)
        {
            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("NEWDATASTRING"));
        }
    }
    ...
}

You'll probably want to play with it a bit to see how it works for you. You can use different Strings to differentiate different data that needs loading. You'll also want to unregister the Receiver at some point, perhaps in onReset(). Let me know if any of this in unclear in the comments, I'll try my best to clarify.