- PVSM.RU - https://www.pvsm.ru -
В статье изложен подход реализации Loader для загрузки разных объектов в одном Activity. В качестве сетевой библиотеки загрузки используется Volley. Метод подходит когда в одном Activity имеется несколько одновременно использующихся фрагментов
public class MainActivity extends ActionBarActivity
implements LoaderManager.LoaderCallbacks<DataHolder>{
...
@Override
public void onLoadFinished(Loader<DataHolder> loader, DataHolder data) {
if ( loader.getId() == DataLoader.LOADER_ICONS_ID ){
doIcons( data.getIcons() );
} else if( loader.getId() == DataLoader.LOADER_STYLES_ID ){
doStyles( data.getStyles() );
} else if( loader.getId() == DataLoader.LOADER_ICONSETS_ID ){
doIconSets( data.getIconSets() );
}
Основная проблема использования Fragment это то что они «живут своей жизнью» (Поправьте меня если это не так). Особенно в момент поворота экрана. Отсюда и конструктор без параметров и static newInstance(..)
All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.
Поэтому используя асинхронную загрузку с помошью Retrofit или Volley нельзя на сто процентов быть уверенным во время возврата из callback в каком состоянии Activity и Fragment. Есть внутренние состояния для FragmentManager, которые можно проверить, но это плохой подход. Например:
// Resolved After Loader implementation
if( !fragmentManager.isDestroyed() ) { // Check problem after rotation screen
Поэтому было решено написать собственный Loader. Feed для теста был выбран Iconfinder [1]. Скажу что feed отдается не всегда по запросу без ошибок. Например можно сделать около 100 запросов, а потом вернется ошибка. Была попытка написать в службу поддержки, но ответа не последовало. Через ~5 секунд ошибка пропадает и возобновляются нормальные запросы. Для теста сойдет
Сам проект доступен на github Android Iconfinder demo [2]
Loader выглядит следующим образом:
public class DataLoader extends Loader<DataHolder> {
public static final String ARGS_URL = "url";
private String urlFeed;
private RequestQueue requestQueue;
private DataHolder dataHolder = new DataHolder();
public static final int LOADER_ICONS_ID = 1;
public static final int LOADER_STYLES_ID = 2;
public static final int LOADER_ICONSETS_ID = 3;
public DataLoader(Context context, Bundle bundle) {
super(context);
urlFeed = bundle.getString(ARGS_URL);
requestQueue = Volley.newRequestQueue(context);
// run only once
onContentChanged();
}
@Override
protected void onStartLoading() {
if (takeContentChanged())
forceLoad();
}
@Override
protected void onStopLoading() {
requestQueue.cancelAll(this);
super.onStopLoading();
}
@Override
protected void onReset() {
requestQueue.cancelAll(this);
super.onReset();
}
@Override
public void onForceLoad() {
super.onForceLoad();
if( getId() == LOADER_STYLES_ID )
doStylesRequest();
else if ( getId() == LOADER_ICONS_ID )
doIconsRequest();
else if ( getId() == LOADER_ICONSETS_ID )
doIconsetsRequest();
}
private void doIconsetsRequest() {
final GsonRequest gsonRequest = new GsonRequest(urlFeed, Iconsets.class, null, new Response.Listener<Iconsets>() {
@Override
public void onResponse(Iconsets iconsets) {
dataHolder.setIconsets(iconsets);
deliverResult(dataHolder);
}
...
}
void doStylesRequest(){
...
}
void doIconsRequest(){
...
}
}
Есть момент. Вернуть данные сразу из callback Volley не получится или правильнее сказать создать на лету DataHolder для deliverResult, поэтому нужен именно член класса DataHolder в котором будут хранится ссылки на объекты
Не забываем про requestQueue.cancelAll(this); когда Loader прерывает работу
Также для Volley сделана небольшая обертка GsonRequest
Но и это еще не все. После вызова onLoadFinished сразу запустить Fragment не получится. Потребуется дополнительная реализация через Handler. На stackoverflow пишут о баге и предлагают именно такое решение:
final int ICONS_HANDLER = 1;
final int STILES_HANDLER = 2;
final int ICONSETS_HANDLER = 3;
@Override
public void onLoadFinished(Loader<DataHolder> loader, DataHolder data) {
if(data == null ) {
// In Loader happened error
AppUtils.showDialog(MainActivity.this, "Error", "Server request error. Try again later", false);
return;
}
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
if(loader.getId() == DataLoader.LOADER_ICONS_ID){
offset += count; // Prepare for next lazy load
b.putParcelable("Icons", data.getIcons());
msg.what = ICONS_HANDLER;
} else if(loader.getId() == DataLoader.LOADER_STYLES_ID){
b.putParcelable("Styles", data.getStyles());
msg.what = STILES_HANDLER;
} else if(loader.getId() == DataLoader.LOADER_ICONSETS_ID){
b.putParcelable("IconSets", data.getIconSets());
msg.what = ICONSETS_HANDLER;
}
msg.setData(b);
mHandler.sendMessage(msg);
}
final Handler mHandler = new Handler(){
public void handleMessage(Message msg) {
Bundle b;
b=msg.getData();
if(msg.what == ICONS_HANDLER){
Icons icons = b.getParcelable("Icons");
fillIcons(icons);
} else if(msg.what == STILES_HANDLER){
Styles styles = b.getParcelable("Styles");
fillStyles(styles);
} else if(msg.what == ICONSETS_HANDLER){
Iconsets iconSets = b.getParcelable("IconSets");
fillIconSets(iconSets);
}
super.handleMessage(msg);
}
};
В MainActivity onStop () прерываем все загрузки:
private void destroyLoaders(){
LoaderManager loaderManager = getSupportLoaderManager();
loaderManager.destroyLoader(DataLoader.LOADER_ICONS_ID);
loaderManager.destroyLoader(DataLoader.LOADER_ICONSETS_ID);
loaderManager.destroyLoader(DataLoader.LOADER_STYLES_ID);
}
@Override
protected void onStop () {
super.onStop();
destroyLoaders();
...
Все методы для загрузки однотипные. В будущем возможно реализовать универсальный типовой метод запроса используя
Class<T> clazz;
Повторюсь, полная реализация проекта доступна на github Android Iconfinder demo [2]. Api iconfinder Api 2.0 [3]
Таким образом получилось приложение с одним Activity и «бутербродом» из Fragments с корректным поворотом экрана
Список литературы:
Loader [4]
Fragment [5]
Implementing Loaders [6]
Автор: app-z
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/87862
Ссылки в тексте:
[1] Iconfinder: http://iconfinder.com
[2] Android Iconfinder demo: https://github.com/app-z/Iconfinder
[3] Api 2.0: https://developer.iconfinder.com/api/2.0/index.html
[4] Loader: http://developer.android.com/reference/android/content/Loader.html
[5] Fragment: http://developer.android.com/reference/android/app/Fragment.html
[6] Implementing Loaders: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
[7] Источник: http://habrahabr.ru/post/254801/
Нажмите здесь для печати.