Вообщем я нашел приемлимый подход, может кому пригодится:
- Делаем слой для запросов. Все поля статические. В нем делаем список листенеров и метод для создания запроса через AsynTask. В ответе асинк такски обходим список листенеров и вызываем нужный коллбек.
- Делаем слой для данных(тоже все статиком) и подписываем его на ответы(добавляем листенер) с наивысшим приоритетом(т.е. чтобы первым вызывался листенер в слое данных)
- Нужные активити подписываем на ответы в onResume и отписываемся в onPause.
- Данные всегда берем только из слоя данных, а активити обновляем по коллбекам из листенера и в onResume
Таким образом:
- Не будет утечек т.к. AsyncTask содержит ссылки только на статики
- Ответ на запрос не придет в мертвую\запауженую активити,
- Ответ не потеряется и данные сохраняется в слое данных.
- Если активити была на паузе или умерла, она подсосет данные из дата слоя в onResume
- Если активити пересоздалась, то ответ придет уже в новую. А если ответ придет между убийством старой и созданием новой, то см. пункты 3 и 4
- Никаких сторонних костылей и оверинжиниринга, можно даже чуток усложнить в некоторых местах(Наример сделать отдельный листенер на изменение данных в дата слое и подписывать активити на него т.е. как писали выше "запрос меняет данные, дата слой оповещает об изменении, потребитель сам забирает данные")
Накидал упрощенный пример кода, некоторые места опущены для простоты.
Слой для запросов
public class Requests
{
public interface OnCompleteListener
{
default void onSomeMethod1(JsonElement answer) {}
default void onSomeMethod2(JsonElement answer) {}
default int getPriority() { return 0; }
}
private static class RequestTask extends AsyncTask<Void, Void, String>
{
public RequestTask(String method)
{
this.method = method;
}
private String method;
@Override
protected String doInBackground(Void ... params)
{
// Тут делаем запрос к серверу
// ...
}
@Override
protected void onPostExecute(String result)
{
handleAnswer(result);
}
}
private static List<OnCompleteListener> listeners = new ArrayList<>();
private static void handleAnswer(String answer)
{
for(OnCompleteListener listener : listeners)
{
if(methodName.equals("api/method1")) listener.onSomeMethod1(answer);
else if(methodName.equals("api/method2")) listener.onSomeMethod2(answer);
}
}
private static void makeRequest(String method)
{
new RequestTask(method).execute();
}
public static void registerListener(OnCompleteListener listener)
{
listeners.add(listener);
Collections.sort(listeners, (l1, l2) -> l2.getPriority() - l1.getPriority());
}
public static void unregisterListener(OnCompleteListener listener)
{
listeners.remove(listener);
}
public static void method1()
{
makeRequest("api/method1");
}
public static void method2()
{
makeRequest("api/method2");
}
}
Активити
public class MainActivity extends AppCompatActivity implements Requests.OnCompleteListener
{
// ...
@Override
protected void onPause()
{
super.onPause();
Requests.unregisterListener(this);
}
@Override
protected void onResume()
{
super.onResume();
Requests.registerListener(this);
updateView();
}
private void updateView()
{
String someData = Data.getSomeData();
// Тут обновляем вьюшки в активити
// ...
}
@Override
public void onMethod1(String answer)
{
updateView();
}
}
Слой данных
public class MyApp extends Application
{
@Override
public void onCreate()
{
super.onCreate();
Requests.registerListener(Data.listener);
}
}
public class Data
{
// Просто какие-то данные для примера
private static List<String> someDataStorage = new ArrayList<>();
public static String getSomeData()
{
return someDataStorage.last();
}
public static Requests.OnCompleteListener listener = new Api.OnCompleteListener()
{
@Override
public void onMethod1(String answer)
{
someDataStorage.add(answer);
}
public void onMethod2(String answer)
{
someDataStorage.add(answer);
}
@Override
public int getPriority()
{
return Integer.MAX_VALUE;
}
};
}