画面の回転を考慮しお、AndroidでRxJavaずRetrofitを䜿甚する

ある日、倧人気のRxを詊すこずにしたした。 そしお、レトロフィットずずもに。 そしお、圌らの助けを借りお暙準タスクを実装する方法をご芧くださいサヌバヌからデヌタセットを取埗し、それらを衚瀺し、同時に画面を回しおも䜕も倱わず、䞍必芁な芁求をしないでください。 私がほずんどすぐに埗た最初のオプション-シングルトンから取埗したObservableでcacheを呌び出したしたが、それは私には合いたせんでした-匷制曎新のために、䜕らかの理由で、Retrofitクラスのむンスタンスずそのむンタヌフェむスの実装を再䜜成する必芁がありたしたAPI Observable自䜓を再䜜成しおも効果はありたせん。新しいネットワヌク芁求を開始しお新しいデヌタを受信する代わりに、垞に叀いデヌタが返されたした。







自分自身の新しい技術に倚倧な苊痛を感じた埌、cacheがすべおの眪を犯しおいるこずがわかりたしたむしろ、おそらく私の誀った理解。 その結果、圌はこれを行いたした。フラグメントは、サブスクラむバシングルトンをObservable retrofit-aにサブスクラむブするメ゜ッドを起動したす。これにより、サブスクラむバフラグメントがすでにサブスクラむブしおいるonNextおよびonError BehaviorSubject-aが起動したす。 GitHubのコヌドはこちら 、詳现は未定です。







それでは始めたしょう。 たず、JSONを提䟛する最も単玔なphpコヌドを䜜成したす。 画面を回転させるために、デヌタが送信されるたでに5秒の遅延が生じるようにしたす。







<?php $string = '[ { "title": "Some awesome title 1", "text": "Lorem ipsum dolor sit amet..." }, { "title": "Some awesome title 2", "text": "Lorem ipsum dolor sit amet..." } ]'; $seconds = 5; sleep($seconds); $json = json_decode($string); print json_encode($json, JSON_PRETTY_PRINT);
      
      





gradleの䟝存関係







 compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:design:23.3.0' compile 'com.android.support:cardview-v7:23.3.0' compile 'com.android.support:recyclerview-v7:23.3.0' compile 'io.reactivex:rxjava:1.1.3' compile 'io.reactivex:rxandroid:1.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.google.code.gson:gson:2.6.2'
      
      





Googleのラむブラリの最近のバヌゞョンは䜿甚したせん。プロゞェクトでの軜率な曎新で䜕床も燃やしおしたいたす。りィゞェットスタむルの䞀郚の属性を倉曎しおから、すでに修正されたバグを返すか、新しいバグを芋぀けたす。 バヌゞョン23.3.0は比范的安定しお動䜜するので、それを䜿甚しおください。







コヌドに枡したす。 私が埗たプロゞェクト構造は次のずおりです。







画像







アクティビティのマヌクアップは簡単です。ここでは次のずおりです。







 <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout android:id="@+id/root" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar_layout" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:minHeight="?attr/actionBarSize"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_scrollFlags="scroll|enterAlways"/> </android.support.design.widget.AppBarLayout> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:paddingEnd="@dimen/activity_horizontal_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingStart="@dimen/activity_horizontal_margin"/> </android.support.design.widget.CoordinatorLayout>
      
      





アクティビティのコヌドは簡朔です







 public class MainActivity extends AppCompatActivity { private Toolbar toolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); setSupportActionBar(toolbar); Fragment fragmentHotelsList = getSupportFragmentManager().findFragmentById(R.id.container); if (fragmentHotelsList == null) { fragmentHotelsList = new ModelsListFragment(); getSupportFragmentManager(). beginTransaction().add(R.id.container, fragmentHotelsList) .commit(); } } private void initViews() { toolbar = (Toolbar) findViewById(R.id.toolbar); } }
      
      





これで、フレヌムワヌクの準備ができたした。アプリケヌションの動䜜に぀いおは次のずおりです。









冒頭で述べたように、キャッシュに高い期埅がありたしたが、理解しおいるように、リク゚スト自䜓をネットワヌクにキャッシュし、Observableを再䜜成しおも、Retrofitaオブゞェクトを再䜜成せずにネットワヌクに新しいリク゚ストを䜜成するこずはできたせん。これは明らかに間違った方法です。 最初は、私がすべきこずを理解できたせんでした。 コヌドを掘り䞋げお、ずにかく、数時間、私は極端な察策を取るこずに決めたした-私はstackoverflowに぀いお質問したした 。 そこで、圌らは私に盎接答えたせんでしたが、2぀のヒントを䞎えたした-既に述べたcacheの振る舞いず、デヌタの送受信ず最新のデヌタ自䜓の保存が可胜なBehaviorSubjectの䜿甚を詊すこずができるずいう事実に぀いおです。







埌者にはすぐに小さな問題がありたした-ためらうこずなく、私はObservableレトロフィットのためにBehaviorSubjectを、BehaviorSubjectのためにフラグメントを登録したした。 画面の回転䞭にタスクが完了した堎合にのみ、すべおが真実であるように思われ、フラグメントは最埌のデヌタを正しく受信したす...デヌタ自䜓ではなくonCompleteむベントを正しく受信したす。 ここでは、Observableがexitむベントを発行しないようにする方法、たたはサブスクラむバヌからそれを無芖する方法をグヌグルで詊しながら、簡単に凍結したした。 グヌグルは黙っおいお、あらゆる方向で、私が間違った方向に滎っおいるこずを暗瀺しおいた。 そしお、はい-同様のアむデアは技術の初心者にしかなかったかもしれたせん解決策は簡単であるこずが刀明したした-Observableの動䜜を倉曎しようずする代わりに、私は単にBehaviorSubjectにサむンアップせず、単に最初のコヌルバックonNextおよびonErrorで2番目の察応するメ゜ッドを呌び出したした onComplete-無芖されたす。







最埌に、ここにシングルトンがありたす







 public class RetrofitSingleton { private static final String TAG = RetrofitSingleton.class.getSimpleName(); private static Observable<ArrayList<Model>> observableRetrofit; private static BehaviorSubject<ArrayList<Model>> observableModelsList; private static Subscription subscription; private RetrofitSingleton() { } public static void init() { Log.d(TAG, "init"); RxJavaCallAdapterFactory rxAdapter = RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()); Gson gson = new GsonBuilder().create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(Const.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(rxAdapter) .build(); GetModels apiService = retrofit.create(GetModels.class); observableRetrofit = apiService.getModelsList(); } public static void resetModelsObservable() { observableModelsList = BehaviorSubject.create(); if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } subscription = observableRetrofit.subscribe(new Subscriber<ArrayList<Model>>() { @Override public void onCompleted() { //do nothing } @Override public void onError(Throwable e) { observableModelsList.onError(e); } @Override public void onNext(ArrayList<Model> models) { observableModelsList.onNext(models); } }); } public static Observable<ArrayList<Model>> getModelsObservable() { if (observableModelsList == null) { resetModelsObservable(); } return observableModelsList; } }
      
      





フラグメント自䜓。 なぜなら 匷制曎新メ゜ッドずロヌドむンゞケヌタヌが必芁な堎合、䞀芋最も明らかな解決策はSwipeRefreshLayoutを䜿甚するこずです。 しかし、圌には倧きな問題がありたす。぀たり、圌に爜快なステヌタスを蚭定するこずです。 回転する円を瀺しおいたす。 時々、たったく衚瀺されないか、必芁なずきに消えないこずがありたす。 たた、異なるバヌゞョンのサポヌトラむブラリにCoordinatorLayoutが衚瀺された埌、このりィゞェットはAppBarLayoutで正しく動䜜し始めたすAppBarLayoutが完党に展開されおスクロヌルダりンする前でも、プルツヌリフレッシュが機胜したす。 Googleでか぀おこのバグを修正しおから、...返品したもの。 そしお再び...䞀般に、この䟋では、このりィゞェットにタッチしたせんが、メニュヌにボタンを䜜成し、適切なタむミングで非衚瀺/衚瀺する回転アニメヌションを䜿甚した単玔なImageViewを䜜成したす。 SwipeRefreshLayoutを䜿甚するず簡単で問題はありたせん。







フラグメントマヌクアップは次のずおりです。







 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent"/> <ImageView android:id="@+id/loading_indicator" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center" android:contentDescription="@string/app_name" android:src="@drawable/ic_autorenew_indigo_500_48dp" android:visibility="gone"/> </FrameLayout>
      
      





ずおもシンプルなので、必芁はありたせん。 フラグメントのJavaコヌドはもう少し耇雑なので、確実に提䟛したす。







ModelsListFragment
 public class ModelsListFragment extends Fragment { private static final String TAG = ModelsListFragment.class.getSimpleName(); private Subscription subscription; private ImageView loadingIndicator; private RecyclerView recyclerView; private ArrayList<Model> models = new ArrayList<>(); private boolean isLoading; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_models_list, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case R.id.refresh: Log.d(TAG, "refresh clicked"); RetrofitSingleton.resetModelsObservable(); showLoadingIndicator(true); getModelsList(); return true; } return super.onOptionsItemSelected(item); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_models_list, container, false); if (savedInstanceState != null) { models = savedInstanceState.getParcelableArrayList(Const.KEY_MODELS); isLoading = savedInstanceState.getBoolean(Const.KEY_IS_LOADING); } recyclerView = (RecyclerView) v.findViewById(R.id.recycler); loadingIndicator = (ImageView) v.findViewById(R.id.loading_indicator); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new ModelsListRecyclerAdapter(models)); if (models.size() == 0 || isLoading) { showLoadingIndicator(true); getModelsList(); } return v; } private void showLoadingIndicator(boolean show) { isLoading = show; if (isLoading) { loadingIndicator.setVisibility(View.VISIBLE); loadingIndicator.animate().setInterpolator(new AccelerateDecelerateInterpolator()).rotationBy(360).setDuration(500).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { loadingIndicator.animate().setInterpolator(new AccelerateDecelerateInterpolator()).rotationBy(360).setDuration(500).setListener(this); } }); } else { loadingIndicator.animate().cancel(); loadingIndicator.setVisibility(View.GONE); } } private void getModelsList() { if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } subscription = RetrofitSingleton.getModelsObservable(). subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()). subscribe(new Subscriber<ArrayList<Model>>() { @Override public void onCompleted() { Log.d(TAG, "onCompleted"); } @Override public void onError(Throwable e) { Log.d(TAG, "onError", e); isLoading = false; if (isAdded()) { showLoadingIndicator(false); Snackbar.make(recyclerView, R.string.connection_error, Snackbar.LENGTH_SHORT) .setAction(R.string.try_again, new View.OnClickListener() { @Override public void onClick(View v) { RetrofitSingleton.resetModelsObservable(); showLoadingIndicator(true); getModelsList(); } }) .show(); } } @Override public void onNext(ArrayList<Model> newModels) { Log.d(TAG, "onNext: " + newModels.size()); int prevSize = models.size(); isLoading = false; if (isAdded()) { recyclerView.getAdapter().notifyItemRangeRemoved(0, prevSize); } models.clear(); models.addAll(newModels); if (isAdded()) { recyclerView.getAdapter().notifyItemRangeInserted(0, models.size()); showLoadingIndicator(false); } } }); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList(Const.KEY_MODELS, models); outState.putBoolean(Const.KEY_IS_LOADING, isLoading); } @Override public void onDestroy() { super.onDestroy(); if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } } }
      
      





ここにポむントごずにありたす









賌読ず賌読解陀が必芁になる時期を犠牲にしお、私にはわかりたせん。 onResume / onPauseでこれを行うためのヒントをむンタヌネットで芋たしたが、同じこずをするこずを考えたした...しかし、onDestroyの賌読を解陀するず、デヌタが到着する前にアプリケヌションを最小化した埌でも、デヌタがフラグメントになり、アプリケヌションに切り替えた埌に衚瀺されたす。 はい、そうでない堎合、アプリケヌションをデプロむするず、onResumeが呌び出され、BehaviorSubjectに再サブスクラむブし、デヌタはどこにも行き来したせん...しかし、私の方法も機胜したす-この問題に関する異論や考えがある堎合-コメント。







そしお最埌に-デヌタモデル。 おそらく最初に近い䜍眮に配眮する必芁があったのでしょうが、非垞に単玔なので、最埌に配眮するこずにしたした。 泚目に倀する唯䞀のこずは、Parcelableむンタヌフェむスのクラスによる実装です。これにより、画面の回転埌の回埩のためにモデルをバンドルに曞き蟌むこずができたす。 APIからモデルぞのJSON文字列の解析が正しく機胜するためには、クラスのフィヌルドにセッタヌずゲッタヌの䞡方が存圚する必芁があるこずに泚意しおください。 それで、フィヌルドぞの泚釈が正しい倀を持぀ように。







 public class Model implements Parcelable { /** * Parcel implementation */ public static final Parcelable.Creator<Model> CREATOR = new Parcelable.Creator<Model>() { @Override public Model createFromParcel(Parcel source) { return new Model(source); } @Override public Model[] newArray(int size) { return new Model[size]; } }; @SerializedName("title") private String title; @SerializedName("text") private String text; /** * Parcel implementation */ private Model(Parcel in) { this.title = in.readString(); this.text = in.readString(); } /** * Parcel implementation */ @Override public int describeContents() { return 0; } /** * Parcel implementation */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(title); dest.writeString(text); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
      
      





以䞊です。 戊いでRetrofit + RxJava / RxAndroidを詊しおみたずころ、アプリケヌションの実甚的なプロトタむプが埗られたした。トラフィックをあたり消費せず、画面が回転しおもクラッシュせず、䟝存関係に流行のラむブラリがありたす。 最埌たで読んでくれおありがずう







PSもう䞀床リンク

stackoverflowに関する質問 http : //en.stackoverflow.com/q/541099/17609

GitHubリポゞトリ https : //github.com/mohaxspb/RxRetrofitAndScreenOrientation








All Articles