2012-05-22 3 views
6

Поскольку в задаче не рекомендуется указывать Context (контекст может быть уничтожен во время выполнения задачи, но хранится в памяти этой задачей), мне было интересно, что же касается Fragments?Безопасно ли поддерживать сильную ссылку на фрагмент в AsyncTask?

Фрагменты управляют ссылкой на свою деятельность, а поддержка сохраняется через setRetainInstance. Могу ли я предположить, что, например, создание нестатической внутренней AsyncTask в Фрагменте безопасно с точки зрения того, что вы не рискуете утечки $this?

ответ

6

Это, как правило, плохая методология для хранения ссылок между потоками, а AsyncTask - это что-то вроде потока.

Все в порядке, если вы убедитесь, что вы разыгрываете его, когда закончите использовать его.

В противном случае вы можете получить утечки памяти.

В этом случае это нормально, потому что ваш Fragment находится в контексте AsyncTask. Когда задача будет выполнена, она потеряет эту ссылку.

Если бы это было сделано в Service, это была бы очень плохая идея.

+2

Риск в этом случае, если вы сохраните ссылку на асинтезу, когда фрагмент мертв. Пользователь может сказать, делает действие, которое выталкивает фрагмент из стека. В этом случае вы обычно не хотите, чтобы состояние Фрагмента существовало больше. Если вы хотите, чтобы состояние asynctask содержало дополнительные данные, которые вы хотели использовать, он сохранил ссылку на фрагмент, даже если фрагмент больше не должен существовать. Я верю, что он будет отстранен от деятельности, так как он прошел обычный жизненный цикл, но он все равно «будет существовать». – DeeV

+0

@DeeV: Да, это именно то, что меня беспокоило. – Matthias

+0

Не могли бы вы просто уйти с назначением его на WeakReference и отменить AsyncTask, если Fragment станет null? – DeeV

2

Ответ Phoenixblade9 верен, но чтобы сделать его полным, я добавлю одно.

Есть отличная замена AsyncTask - AsyncTaskLoader или Loaders вообще. Он управляет своим жизненным циклом в соответствии с контекстом, из которого он был вызван (Activity, Fragment), и реализует кучу слушателей, чтобы помочь вам отделить логику второго потока от потока ui. И это, как правило, невосприимчиво к протекающему контексту.

И не беспокойтесь о названии - это хорошо для сохранения данных.

Как и было обещано, я отправлю свой код для AsyncTaskLoader с возвратом нескольких объектов. Загрузчик идет что-то вроде этого:

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; 
} 

В функции getYourData() я получаю как код сообщения сервера или какой-либо другой код ошибки и ArrayList<SomeItem>. Я могу использовать их в своем фрагменте следующим образом:

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) { 

} 
+0

Да, хороший момент; Я еще не успел использовать «Loader», но быстрый взгляд предполагает, что он страдает от того же недостатка, что и AsyncTask (http://stackoverflow.com/questions/3357477/is-asynctask-really-conceptually-flawed- или-am-i-just-missing-something), то есть не предоставляет управляемую ссылку на Activity/Fragment в обратных вызовах. – Matthias

+0

Но зачем вам нужна ссылка на Activity изнутри Loader? Это его красота - это прекрасное разделение. Работа над потоком ui происходит только в коде активности (через интерфейс LoaderCallbacks), поэтому загрузка (сохранение и т. Д.) Происходит во втором потоке и обновляется поток ui-on ui. Единственным недостатком, о котором я могу думать, является проблема с обновлением ui DURING для загрузки, но это, вероятно, не так сложно сделать с расширениями LoaderCallbacks. –

+0

Что делать, если вы загружаете две разные вещи? Вы не можете реализовать один и тот же интерфейс дважды с использованием разных переменных типа, поэтому обратные вызовы должны быть реализованы в делегате, что затем снова должно управлять ссылками контекста. Хотя, я полагаю, можно создать компоновщик загрузчика, который обеспечивает композитный вывод, который объединяет два разных результата. – Matthias

Смежные вопросы