Update UI from an AsyncTaskLoader

2019-03-20 20:04发布

问题:

I've converted my AsyncTask to an AsyncTaskLoader (mostly to deal with configuration changes). I have a TextView I am using as a progress status and was using onProgressUpdate in the AsyncTask to update it. It doesn't look like AsyncTaskLoader has an equivalent, so during loadInBackground (in the AsyncTaskLoader) I'm using this:

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        ((TextView)getActivity().findViewById(R.id.status)).setText("Updating...");
    }
});

I am using this in a Fragment, which is why I'm using getActivity(). This work pretty well, except when a configuration change happens, like changing the screen orientation. My AsyncTaskLoader keeps running (which is why I'm using an AsyncTaskLoader), but the runOnUiThread seems to get skipped.

Not sure why it's being skipped or if this is the best way to update the UI from an AsyncTaskLoader.

UPDATE:

I ended up reverting back to an AsyncTask as it seems better suited for UI updates. Wish they could merge what works with an AsyncTask with an AsyncTaskLoader.

回答1:

It's actually possible. You essentially need to subclass the AsyncTaskloader and implement a publishMessage() method, which will use a Handler to deliver the progress message to any class that implements the ProgressListener (or whatever you want to call it) interface.

Download this for an example: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html (message me if it goes offline) - this was based of http://habrahabr.ru/post/131560/



回答2:

Emm... you shouldn't be doing this.

because how an anonymous class access parent class Method or Field is by storing an invisible reference to the parent class.

for example you have a Activity:

public class MyActivity
    extends Activity
{
    public void someFunction() { /* do some work over here */ }

    public void someOtherFunction() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                while (true)
                    someFunction();
            }
        };
        new Thread(r).start(); // use it, for example here just make a thread to run it.
    }
}

the compiler will actually generate something like this:

private static class AnonymousRunnable {
    private MyActivity parent;
    public AnonymousRunnable(MyActivity parent) {
        this.parent = parent;
    }

    @Override
    public void run() {
        while (true)
            parent.someFunction();
    }
}

So, when your parent Activity destroys (due to configuration change, for example), and your anonymous class still exists, the whole activity cannot be gc-ed. (because someone still hold a reference.)

THAT BECOMES A MEMORY LEAK AND MAKE YOUR APP GO LIMBO!!!

If it was me, I would implement the "onProgressUpdate()" for loaders like this:

public class MyLoader extends AsyncTaskLoader<Something> {
    private Observable mObservable = new Observable();
    synchronized void addObserver(Observer observer) {
        mObservable.addObserver(observer);
    }
    synchronized void deleteObserver(Observer observer) {
        mObservable.deleteObserver(observer);
    }

    @Override
    public void loadInBackground(CancellationSignal signal)
    {
        for (int i = 0;i < 100;++i)
            mObservable.notifyObservers(new Integer(i));
    }
}

And in your Activity class

public class MyActivity extends Activity {
    private Observer mObserver = new Observer() {
        @Override
        public void update(Observable observable, Object data) {
            final Integer progress = (Integer) data;
            mTextView.post(new Runnable() {
                mTextView.setText(data.toString()); // update your progress....
            });
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreated(savedInstanceState);

        MyLoader loader = (MyLoader) getLoaderManager().initLoader(0, null, this);
        loader.addObserver(mObserver);
    }

    @Override
    public void onDestroy() {
        MyLoader loader = (MyLoader) getLoaderManager().getLoader(0);
        if (loader != null)
            loader.deleteObserver(mObserver);
        super.onDestroy();
    }
}

remember to deleteObserver() during onDestroy() is important, this way the loader don't hold a reference to your activity forever. (the loader will probably be held alive during your Application lifecycle...)



回答3:

Answering my own question, but from what I can tell, AsyncTaskLoader isn't the best to use if you need to update the UI.



回答4:

In the class in which you implement LoaderManager.LoaderCallback (presumably your Activity), there is an onLoadFinished() method which you must override. This is what is returned when the AsyncTaskLoader has finished loading.