Android Volleyカスタムローダー

この記事では、1つのアクティビティで異なるオブジェクトを読み込むためのローダー実装アプローチについて説明します。 Volleyは、ネットワークブートライブラリとして使用されます。 この方法は、1つのアクティビティで複数のフラグメントが同時に使用される場合に適しています



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









フラグメントの使用に関する主な問題は、彼らが「自分の人生を生きている」ことです(そうでない場合は修正してください)。 特に画面を回すとき。 したがって、パラメーターと静的newInstanceのないコンストラクター(..)

newInstance
  /** * Returns a new instance of this fragment * */ public static IconsGridFragment newInstance(Icons icons) { IconsGridFragment fragment = new IconsGridFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_ICONS, icons); fragment.setArguments(args); return fragment; } public IconsGridFragment() { }
      
      







Fragmentのすべてのサブクラスには、引数なしのパブリックコンストラクターを含める必要があります。 フレームワークは、必要に応じて、特に状態の復元中に、フラグメントクラスを再インスタンス化することが多く、このコンストラクターを見つけてインスタンス化する必要があります。 引数なしのコンストラクターを使用できない場合、状態の復元中にランタイム例外が発生することがあります。



したがって、RetrofitまたはVolleyの助けを借りて非同期ロードを使用すると、状態ActivityとFragmentが存在するコールバックから戻るときに、100%確信することはできません。 FragmentManagerにはチェック可能な内部状態がありますが、これは悪いアプローチです。 例:



 // Resolved After Loader implementation if( !fragmentManager.isDestroyed() ) { // Check problem after rotation screen
      
      







したがって、独自のローダーを作成することが決定されました。 テストのフィードはIconfinderによって選択されました 。 フィードは常にエラーなくリクエストに応じて提供されるとは限りません

E / Volley:[979] BasicNetwork.performRequest:httpsの予期しない応答コード429 ...

たとえば、約100個のクエリを実行すると、エラーが返されます。 カスタマーサポートに書き込もうとしましたが、応答がありませんでした。 5秒後、エラーは消え、通常のリクエストが再開します。



Iconfinderからの回答: 429ステータスコードは、リクエストが多すぎることを意味します: developer.iconfinder.com/api/2.0/overview.html#rate-limited

自分で推測できたかもしれません。 この機会に、アニメーションを含むポップアップウィンドウを作成しましたが、すべてフラグメントを使用して同じです。 5秒後にウィンドウを閉じてブートフラグをリセットします。 ft.commit()が実行された直後に、保留中のクローズメッセージが呼び出されます。 正しいものによると、フラグメントに参加するイベントを呼び出す必要があります。 たとえば、onAttachからのコールバック。

OverlayMessageFragmentを使用
  private void closeOverlayDelay() { Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { closeOverlayFragment(); // Reset flag fo continue load after error gone Fragment fragment = getSupportFragmentManager(). findFragmentByTag(IconsGridFragment.class.getSimpleName()); if (fragment != null) ((IconsGridFragment)fragment).resetLoadingFlag(); } }, 5000); } @Override public void onLoadFinished(final Loader<DataHolder> loader, final DataHolder data) { VolleyError volleyError = data.getError(); if (volleyError != null) { if (DEBUG) Log.e(TAG, "volleyError message: " + volleyError.getMessage()); NetworkResponse networkResponse = volleyError.networkResponse; if (networkResponse != null && networkResponse.statusCode == 429) { // HTTP Status Code: 429 if (DEBUG) Log.e(TAG, "volleyError statusCode: " + networkResponse.statusCode); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Fragment fragment = getSupportFragmentManager(). findFragmentByTag(OverlayMessageFragment.class.getSimpleName()); if(fragment == null) { Fragment overlayMessageFragment = OverlayMessageFragment.newInstance("Server Error 429. Too many requests. Try later"); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit); ft.add(R.id.container, overlayMessageFragment, OverlayMessageFragment.class.getSimpleName()); ft.commit(); closeOverlayDelay(); } } }); return; } return; } ...
      
      









プロジェクト自体はgithub Android Iconfinderデモで利用可能です



App-z.net



ローダーは次のとおりです。

 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(){ ... } }
      
      







瞬間があります。 コールバックVolleyからすぐにデータを返すことはできません。または、すぐにdeliverResultのDataHolderを作成する方が正しいため、オブジェクトへの参照が格納されるDataHolderクラスのメンバーが必要です。

requestQueue.cancelAll(これ)を忘れないでください。 ローダーが中断したとき

また、小さなラッパーGsonRequestがVolley用に作成されました



しかし、それだけではありません。 onLoadFinishedを呼び出した後、フラグメントをすぐに起動することはできません。 ハンドラーによる追加の実装が必要になります。 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); } };
      
      







フラグメント自体をBackStackに追加することは残っています。 さらに、すでに作成されているかどうかを確認し、作成されている場合は、単にアイコンをアダプタに追加します。 したがって、ページネーションの類似性が実装されます。または、LazyLoadMoreと呼ばれます。 バックグラウンドでアイコンとフィードをアップロードする

addToBackStack
 private void fillIcons(Icons icons) { // Resolved After Loader implementation //if(!fragmentManager.isDestroyed()) { // Check problem after rotation screen FragmentManager fragmentManager = getSupportFragmentManager(); Fragment iconsGridFragment = fragmentManager.findFragmentByTag(IconsGridFragment.class.getSimpleName()); if (iconsGridFragment != null) { ((IconsGridFragment) iconsGridFragment).addIcons(icons); } else { iconsGridFragment = IconsGridFragment.newInstance(icons); // Add the fragment to the activity, pushing this transaction on to the back stack. FragmentTransaction ft = fragmentManager.beginTransaction(); ft.replace(R.id.container, iconsGridFragment, IconsGridFragment.class.getSimpleName()); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack(null); ft.commit(); } }
      
      







MainActivity.onStop()で、すべてのダウンロードを中断します。

 private void destroyLoaders(){ mHandler.removeCallbacksAndMessages(null); // Because using Fix ! 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;
      
      





次のようになります。

 @Override public void onForceLoad() { super.onForceLoad(); doRequest(DataHolder.getClazz(getId())); }
      
      





DataHolderのクラスはDataHolderItemを実装する必要があります

 public class Icons implements Parcelable, DataHolder.DataHolderItem{
      
      





データローダー
 /** * Created by App-z.net on 02.04.15. */ public class DataLoader extends Loader<DataHolder> { private static final boolean DEBUG = true; private static final String TAG = "DataLoader>"; public static final String ARGS_URL = "url"; private String urlFeed; private RequestQueue requestQueue; private DataHolder dataHolder = new DataHolder(); 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() { if ( DEBUG ) Log.i(TAG, "Loader onStopLoading()"); requestQueue.cancelAll(this); super.onStopLoading(); } @Override protected void onReset() { if ( DEBUG ) Log.i(TAG, "Loader onReset()"); requestQueue.cancelAll(this); super.onReset(); } @Override public void onForceLoad() { super.onForceLoad(); if ( DEBUG ) Log.d(TAG, "Loader onForceLoad() : feedUrl = " + urlFeed); doRequest(DataHolder.getClazz(getId())); } /** * * Get Data */ private void doRequest(Class<?> clazz) { final GsonRequest gsonRequest = new GsonRequest(urlFeed, clazz, null, new Response.Listener<DataHolder.DataHolderItem>() { @Override public void onResponse(DataHolder.DataHolderItem data) { dataHolder.setData(getId(), data); deliverResult(dataHolder); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { if (volleyError != null) if (DEBUG) Log.e(TAG, "volleyError: " + volleyError.getMessage()); deliverResult(null); } }); requestQueue.add(gsonRequest); } }
      
      







データ所有者
 /** * Created by App-z.net on 02.04.15. */ public class 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; private Styles styles; private Icons icons; private Iconsets iconsets; public Styles getStyles(){ return styles; } private void setStyles(Styles styles){ this.styles = styles; } public Iconsets getIconsets() { return iconsets; } private void setIconsets(Iconsets iconsets){ this.iconsets = iconsets; } public Icons getIcons(){ return icons; } private void setIcons(Icons icons){ this.icons = icons; } public void setData(int dataId, DataHolderItem item){ switch (dataId){ case LOADER_ICONS_ID: setIcons((Icons)item); break; case LOADER_STYLES_ID: setStyles((Styles)item); break; case LOADER_ICONSETS_ID: setIconsets((Iconsets)item); break; default: assert false : "Error LOADER ID"; } } public DataHolderItem getData(int dataId){ switch (dataId){ case LOADER_ICONS_ID: return getIcons(); case LOADER_STYLES_ID: return getStyles(); case LOADER_ICONSETS_ID: return getIconsets(); default: assert false : "Error LOADER ID"; } return null; } public static Class<?> getClazz(int dataId){ switch (dataId){ case LOADER_ICONS_ID: return Icons.class; case LOADER_STYLES_ID: return Styles.class; case LOADER_ICONSETS_ID: return Iconsets.class; default: assert false : "Error LOADER ID"; } return null; } public interface DataHolderItem{ } }
      
      







繰り返しますが、プロジェクトの完全な実装はgithub Android Iconfinder demoで利用できます。 ApiアイコンファインダーApi 2.0



したがって、1つのActivityとFragmentsからの「サンドイッチ」が正しい画面回転を持つアプリケーションを取得しました



参照:

ローダー

フラグメント

ローダーの実装



All Articles