Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to keep a strong reference to a Fragment in an AsyncTask?

Since it's not recommended to keep a strong reference to a Context in a task (the context may get destroyed while the task is still running, but kept in memory by the task), I was wondering if the same applies to Fragments?

Fragments manage their activity reference, and support being retained via setRetainInstance. Can I assume that e.g. creating a non-static inner AsyncTask in a Fragment is safe in terms of not risking to leak $this?

like image 662
Matthias Avatar asked May 22 '12 19:05

Matthias


People also ask

Why should you use a fragment rather than an activity?

By making each screen a separate Fragment, this data passing headache is completely avoided. Fragments always exist within the context of a given Activity and can always access that Activity.

What are the benefits of fragments?

Fragments allow such designs without the need for you to manage complex changes to the view hierarchy. By dividing the layout of an activity into fragments, you become able to modify the activity's appearance at runtime and preserve those changes in a back stack that's managed by the activity.

What are the benefits of using fragments in Android?

The Fragment class in Android is used to build dynamic User Interfaces and should be used within the activity. The biggest advantage of using fragments is that it simplifies the task of creating UI for multiple screen sizes. An activity can contain any number of fragments.


2 Answers

It's generally bad methodology to keep references between threads, and an AsyncTask is something like a thread.

It's okay, as long as you make sure that you dereference it when you are done using it.

Otherwise, you could get memory leaks.

In this case, it's okay because your Fragment is in the context of AsyncTask. When the task is done, it will lose that reference.

If this were being done in a Service, it would be a very bad idea.

like image 147
Codeman Avatar answered Sep 27 '22 22:09

Codeman


Phoenixblade9's answer is correct, but to make it full I'd add one thing.

There's a great replacement for AsyncTask - AsyncTaskLoader, or Loaders generally. It manages its lifecycle according to the context from which it's been called (Activity, Fragment) and implements a bunch of listeners to help you separate the logic of a second thread from the ui thread. And it's generally immune to leaking context.

And don't bother the name - it's good for saving data as well.


As promised I'll post my code for AsyncTaskLoader withg multiple objects returned. The loader goes something like this:

public class ItemsLoader extends AsyncTaskLoader<HashMap<String, Object>>{

HashMap<String, Object> returned;
ArrayList<SomeItem> items;
Context cxt;

public EventsLoader(Context context) {
    super(context);
    //here you can initialize your vars and get your context if you need it inside
}

@Override
public HashMap<String, Object> loadInBackground() {


    returned = getYourData();

    return returned;

}

@Override
public void deliverResult(HashMap<String, Object> returned) {
    if (isReset()) {
        return;
    }

    this.returned = returned;

    super.deliverResult(returned);
}

@Override
protected void onStartLoading() {
    if (returned != null) {
        deliverResult(returned);
    }

    if (takeContentChanged() || returned == null) {
        forceLoad();
    }
}

@Override
protected void onStopLoading() {
    cancelLoad();
}

@Override
protected void onReset() {
    super.onReset();

    onStopLoading();

    returned = null;
}

In the getYourData() function I get both the server message code or some other error code and an ArrayList<SomeItem>. I can use them in my Fragment like this:

public class ItemListFragment extends ListFragment implements LoaderCallbacks<HashMap<String, Object>>{

private LoaderManager lm;

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

    lm = getLoaderManager();

    Bundle args = new Bundle();
args.putInt("someId", someId);
lm.initLoader(0, args, this);
}


@Override
public Loader<HashMap<String, Object>> onCreateLoader(int arg0, Bundle args) {
    ItemsLoader loader = new ItemsLoader(getActivity(), args.getInt("someId"));
    return loader;
}

@Override
public void onLoadFinished(Loader<HashMap<String, Object>> loader, HashMap<String, Object> data) {

    if(data!=null){ if(data.containsKey("items")){ 
        ArrayList<SomeItem> items = (ArrayList<EventItem>)data.get("items");

    } else { //error
        int error = 0;
        if(data.containsKey("error")){
            error = (Integer) data.get("error");
        }
            }

}

@Override
public void onLoaderReset(Loader<HashMap<String, Object>> arg0) {

}
like image 26
Michał Klimczak Avatar answered Sep 27 '22 22:09

Michał Klimczak