Android Ditches、パート3:SDKおよびRxJava(最終版)

Android SDKと「サプライズ」はほぼ双子です。 development.android.comを心から知っているかもしれませんが、同時に、ボタンプログレスバーフォームよりもクールなことをしようとしながら髪を引き裂き続けます。
これは、Android Cellsに関する一連の記事の最後の3番目の部分です。 実際、もちろん十数個あるはずですが、私はあまりにも控えめです。 今回、ようやく遭遇したSDKのトラブルについてお話ししました。また、現在人気のあるReactiveXテクノロジーについても触れます。
一般的に、Android SDK、RxJava、Ditches-始めましょう!
画像

前のパーツ:


1. actionLayoutが設定されている場合、 Activity.onOptionsItemSelected()は呼び出されません


状況

テストタスクを実行したら。 それは退屈で単調で...古いものでした。 とても古い。 PSDを前世紀のように。 まあ、ポイントではありません。 すべての主要なポイントを終えたので、私はすべてのインデント(ハッキング、ペン、定規によると昔ながらの方法)を差し引くことを設定しました。 アプリケーションとPSDの画面で不愉快なメニューの不一致が見つかるまで、事態はうまくいきました。 アイコンは同じでしたが、 パディングは同じではありませんでした。 冒険家として、アイコンを縮小しませんでしたが、 MenuItemの actionLayoutプロパティを使用することにしました。 必要なパラメーターを含む新しいレイアウトをすばやく追加し、エミュレーターのアイコンのインデントを再確認して、ソリューションを送信し、日没になりました。

状況

答えが来たときの驚きを想像してください(逐語的):「編集は機能しません。」 ところで、私はこの方法とそれの両方でアプリケーションをテストしましたが、何かを見逃してはいけませんでした。 パニックはまた、正確に何が機能しなかったのかが明確ではなかった答えの簡潔な形式によって悪化しました...
...幸いなことに、私は長い間検索する必要はありませんでした。 タイトルからすでに明らかなように、カスタムレイアウトを設定する場合、 onOptionsItemSelected()は単に無視されます
なんで?
画像

それ以来、 Androidではジョークがひどく、デザインを変更してもアプリケーションの動作が変化する可能性があることをはっきりと認識しました。 さて、いつものように、 解決策
回避策
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); final Menu m = menu; final MenuItem item = menu.findItem(R.id.your_menu_item); item.getActionView().setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onOptionsItemSelected(item); } }); return true; } 



2. MVC / MVP / MVVMと他の美しい言葉対。 スクリーンスイッチャー


状況

おそらく、私たち一人一人は少なくとも一度MVCとその親類について聞いたことがあります。 MVVMはまだAndroid上に構築できません(私は嘘をつきます、可能ですが、これまでのところベータ版 )が、 MVCとMVPが積極的に使用されています。 しかし、どのように? Android開発者なら誰でも、画面を回転させると、 アクティビティフラグメントが完全に破壊されることを知っています(さらに、それらに加えてほんの一握りの神経も)。 たとえば、 MVPを適用すると同時に、 Presenterに害を与えずに画面を回転させる方法はありますか?

解決策

そして、すでに3つの主要なソリューションがあります:
  1. Fragment.setRetainInstance()をすべての場所で使用すれば、あなたは幸せになります」-または、新参者は通常言う。 残念ながら、このようなソリューションは、最初は保存されますが、すべての計画を破棄しますが、必要に応じて、 PresenterActivityに追加します。 そして、これが起こります。 ほとんどの場合、 DualPaneが導入されています
    どのようなDualPaneですか?
    画像

    また、 setRetainInstance()には、その利点を支持しないバグあります。 しかし、それについては後で。
  2. ライブラリ、フレームワークなど。 幸いなことに、それらのかなりの数があります: モクシー(同様のトピックに関する記事を読む必要があります)モスビーモルタルなど。 それらのいくつかは、いわゆるビューステートを復元しようとすると、同時にあなたの神経を救います。
  3. さて、「クレイジーハンド」アプローチ-Singletonを作成し、 GetUniqueId()メソッドを指定します(呼び出し時にインクリメントしてAtomicInteger値を返します)。 プレゼンターを作成し、以前に受け取ったIDActivity / Fragmentの バンドルに保存し、 プレゼンターIDでアクセスできるシングルトン内に保存します 。 できた プレゼンターライフサイクルから独立しています (まあ、 シングルトンにありますonDestroy()で 発表者を削除することを忘れないでください!


3.写真付きのTextView


そして、いつものように、溝ではなくアドバイスです。
このようなことをする必要がある場合はどうしますか?
碑文のアイコン
画像

あなたの答えが「Pf! 何の問題? LinearLayoutまたはRelativeLayoutの TextViewおよびImageView 」-このアドバイスはあなたにぴったりです。 奇妙なことに、 TextViewにはTextView.drawable {ANY_SIDE}プロパティとTextView.drawablePaddingがあります。 それらは、本来あるべきことを正確に行い、ネストされたレイアウトは行いません。
TextView.drawable {ANY_SIDE}がどのように見えるか
画像

正直なところ、 TextViewから画像に関連するプロパティを探すことは決してなかったので、私自身はこのプロパティについて比較的最近、そして偶然に知りました。

4. Fragment.setRetainInstance()を使用すると、 アクティビティAppCompat )の直接の子孫のみを保存できます。


状況

あなたの父親がジョン・タイターであり、あなたの母親がサラ・コナーであり、あなたが遠い2013年から来た場合、あなたはまだ埋め込まれたフラグメントに対する憎しみの新鮮な気持ちを持っています。 実際、当時、彼らの「不従順」( tytstyts )に対処することは非常に困難で、「 埋め込まれたフラグメントを含むコード」はすぐに「松葉杖を備えたコード」に変わりました。
当時、私はプログラミングを始めたばかりで、そのような恐怖を読んでいたので、 埋め込まれた断片を手に入れないことを誓いました。
時間が経つにつれて、私はフラグメントのネストを使用せず、何らかの理由でこの計画のすべてのニュースが通り過ぎました...そして、突然、 フラグメントが完全にネストされたというニュースに遭遇しました(ごめん、リンクをsoきました)人生全般==おとぎ話。 そして、私は何を言うことができます-私は信じていました! プロジェクトを作成し、 フラグメントハッシュ プレゼンターを色変換する例をロールアップし(これにより、 保持が機能しているかどうかすぐに判断できるようになります)、起動し、画面を回転させて...

そして..?

そして、週末全体を通して、第1レベルのフラグメント( アクティビティ自体に保存されているフラグメント)のみが保存される理由を探しました。 当然、私が最初に罪を犯し始めたのは自分自身でした。 ペイントコードから始まり、 MVPの撲滅で終わるコード全体を調べ、 SDKのソースを調べ、 Nested Fragmentsの大量の投稿を掘り下げまし (そして、残念な開発者でさえあるようなクラウドがあります)。そして、先週末の終わりまでに発見しました(!) これ
読むのが面倒な人のために: Fragment.setRetainInstance()FragmentManagerを使用してFragmentが破壊されるのを防ぎます-それはすべて大丈夫です。 しかし、何らかの理由で、開発者の1人がmFragmentManager = null;という行を追加しました 、およびFragment実装のみ-Activityがすべて問題ない理由です!
なぜ、なぜ、そしてどのように判明したかは、未回答になる興味深い質問です。 この1行のバグは2.5バージョンでも続きます。 前のリンク( 怠け者の場合 )は、 リフレクション回避策について説明しています。 残念ながら、今のところこれが問題を解決する唯一の方法です(もちろん、ソースコードをプロジェクトに完全にコピーすることは別として)。 問題自体は、 バグトラッカーで詳細に説明されています。

psタイムマシンを売らないwon(・_├┬┴┬┴

5. RxJavaobserveOn()subscribeOn()の違い


おそらく、私は最も単純なものであると同時に最も重要なものから始めます。
Rxを起動したばかりのとき、これらの方法の違いは私には完全に不明瞭でした。 論理的に、 subscribeOn()は、 subscribe()が呼び出されるスケジューラを変更します 。 しかし... ...別のロジックの観点では、 SubscriberObserverを継承しますが、 Observerは何をしますか? おそらく観察する 。 そしてここに認知的不協和音がありました。 グーグルも、 スタックオーバーフローも、 公式のビー玉でさえ、明快さをもたらしませんでした。 しかし、もちろん、そのような知識は非常に重要であり、 Schedulersで1週間または2つのミスを犯した後、それだけで生まれました。
私はよく友人からこの質問を聞き、時にはさまざまなフォーラムで出くわします。そのため、結果を心配せずに、「反応する」、またはこれらの演算子を単純に直感的に使用する人の説明を次に示します。
コード
 Observable.just(null) .doOnNext(v0id -> Log.i("TAG", "0")) //  : computation .observeOn(Schedulers.newThread()) .doOnNext(v0id -> Log.i("TAG", "1")) //  : newThread .observeOn(Schedulers.io()) // io .doOnNext(v0id -> Log.i("TAG", "2"))  : io .subscribeOn(Schedulers.computation()) .subscribe(v0id -> Log.i("TAG", "3")); // -  : io 


(私自身の経験から)最も紛らわしいことは、 ReactiveXがスローガン「すべてはストリームです」で前進していることだと思います 。 その結果、新規参入者は、各演算子がそれに続く演算子のみに影響し、ストリーム全体には影響しないことを期待しています。 しかし、これはそうではありません。 たとえば、 startWith()はストリームの開始に影響し、 finallyDoはストリームの終了に影響します。
Rxソースコードでの名前については、データはObservableクラスではなく(突然ですか?)、 OnSubscribeクラスによって生成されることがわかります。 これがsubscribeOn()演算子の紛らわしい名前の由来であると思います。
ちなみに、 Frodoのログ記録に慣れておくように、初心者や経験豊富な愛好家であっても強くお勧めしますRxコードの借方記入は依然として作業であるため、時間を大幅に節約してください。

6. RxJava演算子トランスフォーマー


状況

Rxコードが大きくなることがよくあるので、どうにかしてそれを減らしたいと思います。 チェーンの形でメソッドを呼び出す方法は 、はい、良いですが、その再利用はゼロです-あなたは小さなことなどを行うすべての同じメソッドを呼び出す必要があるたびに など
このようなニーズに直面した初心者は、OOPの観点から考え始め、すべてが本当に悪い場合は静的メソッドを作成し、呼び出しのチェーンの始まりをラップします。 時間内にこのアプローチを廃止しなければ、 Observableごとに3〜4個のラッパーになります。
実際の製品のいずれかの実際のコード
 RxUtils.HandleErrors( RxUtils.FireGlobalEvents( RxUtils.SaveToCaches( Observable.defer(() -> storeApi.getAll(filter)).subscribeOn(Schedulers.io()), caches) , new StoreDataLoadedEvent() ) ).subscribe(storeDataObserver); 


将来的には、これはコードが何をしているのかを理解したい人や何かを変更したい人に多くの問題をもたらすでしょう。

そして今何?

チェーンメソッドは読みやすいため、優れています。 できるだけ早く独自の演算子とトランスを作成する方法を学ぶことをお勧めします。 思ったより簡単です。 Operatorはデータユニット(たとえば、 onNext()一度に1回呼び出す)で動作し、 TransformerObservable自体を変換することを理解することが重要です(ここでは、通常のmap()/ doOnNext()などを1つの全体に結合できます)。

すべて子供向けのゲームで終了しました。 溝に移りましょう。

7. RxJavaサブスクリプションの実装におけるカオス


状況

だから、あなたは反応的です! あなたはそれを試してみました、あなたはそれを好きでした、あなたはもっと欲しいです! Rxですべてのテスト項目をすでに書きました。 Rxでホームプロジェクトを書き換えています。 Rxにあなたの猫を教えます。 そして今、Grailを作成するときがきました。Rxでアーキテクチャ全体を構築するためです。 あなたは準備ができています、あなたは頻繁に、そしてだらしないように呼吸し、そして...始めます... moooey preeelst

なんで?

残念ながら、上記は私のためです。 Rxのパワーに非常に驚いたので、アーキテクチャを書くためのすべてのアプローチを完全に再考することにしました。 MVP + Rxを使用してMVVMを再発明しようとしたと言えます。
ただし、初心者の最大の間違いは認めます。Rxを理解したと判断しました。
それをよく理解するには、いくつかのRxアプリケーションを書くだけでは不十分です 。 クリックとジャンプを3つの異なるソースからの写真、ビデオ、テストデータにリンクするよりもタスクが複雑になると、 バックプレッシャーなどの突然の問題が発生します。 そして、 背圧を知っていると決めると、あなたはProducerについて何も知らないことに気付くでしょう(通常のドキュメントさえもありません)...何か私は逸れます(そして記事の終わりに、それはなぜ明らかになります)。
一般に、問題の本質はロジックにあり、実際に利用可能なものとは逆になります。
リスニングは通常どうですか?
 //... data.registerListener(listener); // data.mListener == listener //... data.unregisterListener(); // data.mListener == null 


つまり、データソースにはリスナーへの参照が格納されます。
しかし、Rxではどうなりますか? (慎重に、今では断片は少し田舎のコードになります)
observer.unsubscribe() 500ミリ秒で

コード
 Observable.interval(300, TimeUnit.MILLISECONDS).map(i -> "t1-" + i).subscribe(observer); l("interval-1"); Observable.interval(330, TimeUnit.MILLISECONDS).map(i -> "t2-" + i).subscribe(observer); l("interval-2"); Observable.timer(500, TimeUnit.MILLISECONDS).map(i -> "t3-" + i).subscribe(ignored -> observer.unsubscribe()); 


結果
 interval-1 interval-2 t1-0 t2-0 


これが最も期待される結果だと思います。 ありすべてとすべてを再作成する方法)。

500msでsubscription1.unsubscribe()

次に、 Subscriberからではなく、 Subscriptionからサブスクライブを解除してみましょう。 論理的な観点から、 サブスクリプションObserverObservableを1:1としてバインドし、何かから選択的にサブスクリプションを解除できるようにする必要がありますが、...
コード
 Subscription subscription1 = Observable.interval(300, TimeUnit.MILLISECONDS).map(i -> "t1-" + i).subscribe(observer); l("interval-1"); Observable.interval(330, TimeUnit.MILLISECONDS).map(i -> "t2-" + i).subscribe(observer); l("interval-2"); Observable.timer(500, TimeUnit.MILLISECONDS).map(i -> "t3-" + i).subscribe(ignored -> subscription1.unsubscribe()); 


結果
 interval-1 interval-2 t1-0 t2-0 


...突然、結果はまったく同じになります。 Rxを初めて知ったとき、これについては知りませんでしたが、それがうまくいくと思って長い間同じようなアプローチを使用していました。 実際には、 SubscriberObserverインターフェイスと... Subscriptionを実装しています。 つまり 私たちが持っているサブスクリプションは同じオブザーバーです! ここにそのようなターンがあります。

Observable.defer()およびObservable.fromCallable()

defer()は、 RxObservable.flatMap()と同等)で最もよく使用される演算子の1つだと思います。 そのタスクは、 subscribe()が呼び出されるまでObservableデータの初期化を延期することです。 試してみましょう:
コード
 Observable.defer(() -> Observable.just("s1")).subscribe(observer); l("just-1"); Observable.defer(() -> Observable.just("s2")).subscribe(observer); l("just-2"); observer.unsubscribe(); Observable.defer(() -> Observable.just("s3")).subscribe(observer); l("just-3"); 


結果
 s1 just-1 s2 just-2 s3 just-3 


「そして何? 予期しないことはありません」とあなたは言います。 「おそらく」と答えます。
しかし、 Observable.just()の作成にうんざりしている場合はどうでしょうか? Rxには答えがあります。 グーグルで簡単に検索すると、 Observable.fromCallable()メソッドが見つかります。これにより、 Observableではなく通常のラムダを延期できます。 私達は試みます:
コード
 Observable.fromCallable(() -> "z1").subscribe(observer); l("callable-1"); Observable.fromCallable(() -> "z2").subscribe(observer); l("callable-2"); observer.unsubscribe(); Observable.fromCallable(() -> "z3").subscribe(observer); l("callable-3"); 


結果(注意!画面から子供とハムスターを削除します)
 z1 callable-1 callable-2 callable-3 


異なるソースデータでのみ同じことをするメソッドですが、そのような違いがあるように思われます。 この結果で最もわかりにくい(論理的に考えると)のは、 z1-z2-callableではないことです (これまでに説明したすべてを信じている場合)。つまり、 z1-callable ...です。 問題は何ですか?

事実は...

ここまでがポイントです。 実際、多くの演算子はさまざまな方法で記述されています。 次のonNext()の前の誰かがSubscriberサブスクリプションをチェックし、誰かが発行後にチェックしますが、最後のonNext()まで、そして誰かが前後にチェックします。 これにより、予想される結果に混乱が生じます。 しかし、これでもObservable.fromCallable()の動作を説明していません。
Rx内には、 SafeSubscriberクラスがあります。 これはまさに、メインのRxコントラクトを担当するクラスです(「 onError()の後、 onNext()はなくなり、 サブスクリプション解除などが発生する」など)。 そして、それをオペレーターで使用する必要があるか( SafeSubscriber )かは、どこにも登録されていません。 一般に、 Observable.fromCallable()は通常のsubscribe ()を呼び出します。したがって、 SafeSubscriberは暗黙的に作成され、 発行後にunsubscribe ()が発生しますが、 Observable.defer()unsafeSubscribe()を呼び出します。 したがって、実際には(突然!)このObservable.defer()は悪いものであり、 Observable.fromCallable()ではありません。

8. RxJava :手動でのサブスクライブ解除/サブスクライブの代わりにrepeatWhen()


状況

X秒ごとにデータを更新する必要があります。 もちろん、古いデータをダウンロードするまで、新しいデータをダウンロードすることはできません(これは、ラグ、バグ、およびその他の不正のために可能です)。 どうする
そして、すべてが応答して始まります: Observable.interttle()Observable.throttle()またはAtomicBoolean 、そして一部は手動unsubscribe ()を介してそれを行うことさえできます。 実際、すべてがはるかに単純です。

解決策

Rxにはすべての場合に演算子があるようです。 だから今です。 あなたのためにすべてを行うrepeatWhen()メソッドがあります-指定された間隔でObservableに再サブスクライブします:
RepeatWhen()の例
 Log.i("MY_TAG", "Loading data"); Observable.defer(() -> api.loadData())) .doOnNext(data -> view.setDataWithNotify(data)) .repeatWhen(completed -> completed.delay(7_777, TimeUnit.MILLISECONDS)) .subscribe( data -> Log.i("MY_TAG", "Data loaded"), e -> {}, v0id -> Log.i("MY_TAG", "Loading data")); // "Loading data" -   ; "Data loaded" -    ~8 . 


唯一のマイナス点は、最初はこの方法がどのように機能するかが完全に明確ではないことです。 しかし、いつものように、 repeatWhen()/ retryWhen()に関する良い記事があります。

再試行するとき

ちなみに、 repeatWhen()のほかに、同じことをするretryWhen()もありますが、 onError()のためです。 ただし、 repeatWhen()とは異なり、 retryWhen()役立つ状況非常に具体的です。 上記の場合、おそらく追加することができます。 ただし、一般的には、 Rx Plugins / Hooksを使用して、目的のエラーのためにグローバルハンドラーをハングさせる方が適切です。 これにより、エラーが発生した場合にObservableを再サブスクライブできるだけでなく、ユーザーに通知することもできます(たとえば、 SocketTimeoutExceptionに似たようなものを使用します)。

追加。 RxJava :16


そして最後に、それが私がキュベットについて書き始めた理由です。 私が人生の2週間を費やしたが、どのような...魔法がそこに起こっているのかまだわからないという問題...しかし、順番に見てみましょう。

状況

認証画面を作成し、誤って入力されたフィールドをチェックし、3番目のエラーごとに特別な警告を発行する必要があります。
タスク自体は難しくないため、 Rxの 「テストサイト」として選択しました。 サーバーからデータをダウンロードするだけのビジネスとは異なるビジネスでRxがどのように動作するのかを確認します。
そのため、コードは次のようになりました。
ログインエラー処理コード
 PublishSubject<String> wrongPasswordSubject = PublishSubject.create(); /*...*/ wrongPasswordSubject .compose(IndexingTransformer.Create()) .map(indexed -> String.format(((indexed.index % 3 == 0) ? "GREAT ERROR" : "Simple error") + " #%d : %s", indexed.index, indexed.value)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(message -> getView().setMessage(message)); 


ボタン処理コード[サインイン]
 private void setSignInAction() { getView().getSignInButtonObservable() .observeOn(AndroidSchedulers.mainThread()) .doOnNext((v) -> getView().setSigningInState()) //    .observeOn(Schedulers.newThread()) .withLatestFrom(formDataSubject, (v, formData) -> formData) .map(formData -> auth(formData.login, formData.password)) // .   WrongLoginOrPassException .lift(new SuppressErrorOperator<>(throwable -> wrongPasswordSubject.onNext(throwable.getMessage()))) //      .compose(new UnObservableTransformer<>()) //       flatMap().      .observeOn(AndroidSchedulers.mainThread()) .subscribe(user -> getView().setSignedInState(user)); // happy end } 


私たちは、 Rxスタイルのコードに対する主張を延期します。すべてが悪い、私はそれを自分で知っています。 それはそうではなく、ずっと前に書かれました。
したがって、 getView()。GetSignInButtonObservable()は、 [Sign In]ボタンをクリックしたときにRxAndroidから受信したObservableを返します。 これは注目に値するものです 。つまり、 完了することはできません。 イベントはそれから始まり、 マップ()を通過します。ここで認証が行われ、さらにチェーンに沿って進みます。 エラーが発生した場合、カスタムオペレーターはエラーキャッチし、それ以上見逃しません。
SuppressErrorOperator
 public final class SuppressErrorOperator<T> implements Observable.Operator<T, T> { final Action1<Throwable> errorHandler; public SuppressErrorOperator(Action1<Throwable> errorHandler) { this.errorHandler = errorHandler; } @Override public Subscriber<? super T> call(final Subscriber<? super T> subscriber) { return new Subscriber<T>(subscriber) { @Override public void onCompleted() { subscriber.onCompleted(); } @Override public void onError(Throwable e) { errorHandler.call(e); //  ,    } @Override public void onNext(T t) { subscriber.onNext(t); } }; } } 


だから問題は。 このコードの何が問題になっていますか?
彼らがこれについて私に尋ねたら、私は今でも答えます:「すべては大丈夫です。」 Subscriptionを保存する場所がないため、メモリリークを除きます。 はい、 onNextのみがsubscribeで上書きされますが、他のメソッドは呼び出されません。 すべて順調です、私たちは取り組んでいます。

痛み

ネクタイ

そして、ここから最も奇妙なことが始まります。 コードは本当に機能します。 しかし、私は細心の注意を払っているので、承認ボタンをクリックすることにしました...何度も。 そして、突然、5番目の "GREAT ERROR"の後、何らかの理由で認証の進行状況バー( setSigningInState()で設定)が機能しなかったことを発見しました(この機能は[サインイン]ボタンもオフにします)。
「うーん」と思う。 UIを担当するFragmentの関数を再確認しました(突然、何かが挿入されました)。 auth()関数を確認しました。テストのタイムアウトを設定している可能性があります。 いや 大丈夫です。
それから、私はそれがフローのレースであると決めました。 私はそれを再び開始し、もう一度チェックしました...まさに5「GREAT ERROR」と無限のプログレスバーの停滞。 そして緊張した。 再び起動し、その後ますます。 まさに5! 5番目の「GREAT ERROR」の直後に、ボタンが押しても反応しなくなると、進行状況バーが回転して無音になります。
「わかりました」、「 setSigningInState()を削除します。」 Androidは人と遊ぶのが大好きです。 突然、 SDKの何かが壊れました。唯一のことは、ボタンをもう一度クリックすることができず、そのハンドラーが機能しないことだけではありません。」 いや 助けにはなりませんでした。
この瞬間までに、私はすでに非常に緊張していました。 LogCatは空で、エラーはなく、アプリケーションは動作しており、ハングしていませんでした。 ハンドラーはもう処理しません。

分析

タスク自体が私を欺いたことが判明しました。 「GREAT ERROR」の数を数えましたが、実際にはボタンを押した数を数えなければなりませんでした。 正確に16。数値は変更されましたが、状況は変わりません。
したがって、不要なものをすべて削除した後の次の試みのコードは次のとおりです。
doOnNext()にログがあるコード
 private void setSignInAction() { getView().getSignInButtonObservable() .observeOn(AndroidSchedulers.mainThread()) .doOnNext((v) -> l("1")) .observeOn(Schedulers.newThread()) .doOnNext((v) -> l("2")) .map(v -> { throw new RuntimeException(); }) .lift(new SuppressErrorOperator<>(throwable -> wrongPasswordSubject.onNext(throwable.getMessage()))) .doOnNext((v) -> l("3")) .observeOn(AndroidSchedulers.mainThread()) .doOnNext((v) -> l("4")) .subscribe(user -> runOnView(view -> view.setTextString("ON NEXT"))); } 


そして、状況はさらに奇妙になりました。 1〜15回のクリックが正常に行われ、数字の「1」と「2」が表示されましたが、16回目のログの最後の行は「1」でした。 エラージェネレーターに到達しませんでした!
「だから、それは例外に関するものではないのでしょうか?!」と思いました。 throw new RuntimeException()return nullおよび...で置き換え、すべてが機能し、クリックした回数に関係なく4桁すべてが表示されます(すべてがフリーズすることを期待して100回以上クリックしましたが、いいえ)。
この瞬間までに、2日目または3日目はすでに私の苦痛とその時までに持っていたすべてのことでした:



来週、私は完全に手がかりを探すためにReactiveXの公式ウェブサイトを調べました。私はgithubのRxJavaリポジトリ、またはそのwiki を調べましたが、まだ答えが見つからなかったため、必死のステップに決め、...「pokeメソッド」を使い始めました。
できる限りのことを試してみて、最終的に問題を解決したものを見つけました:onBackpressureBuffer()。何が背圧に説明ウィキRxJava'vskogoリポジトリは、と私が言ったように、それは私が、検索時に読み取られていましたが、魔法はまだ魔法で残っていました。
知らない人のために。背圧問題オペレーターが前のオペレーターからのデータを処理する時間がないときに発生します。最も顕著な例はzip()です。まず、オペレータは、その要素1毎に生成した場合、および秒-あたり1時間、次にジッパー()が屈曲あろう。onBackpressureBuffer() -演算子によって生成された全時間のすべての値を格納する配列を暗黙的に入力します。したがって、zip()は意図したとおりに動作します(ただし、最終的にOutOfMemoryExceptionを取得します)。
それから質問に応じて、なぜonBackpressureBuffer()が役立ったのでしょうか?私は両方の方法でプログラムを実行しました。私もタイマーをクリックしてみました[サインイン] 1分に1回だけです(まあ、にしないでください。突然Flashになり、クリックが速すぎますか?)。もちろん、これは役に立ちませんでした。

ファイナル

それでも結局、observeOn()の時点でコードが死んでいることに気付きました。 「そして、ここはどちら側ですか?」とあなたは尋ねます。 「¯\ _(ツ)_ /¯」-答えます。
そのコード、コードonBackpressureBuffer()およびObservable構造全体を研究するのに多くの時間がかかりました。それから、OnSubscribeクラス、Producer、その他の興味深いことを学びました...しかし、これだけでは手がかりが得られませんでした。Rxのソースを完全に把握したと言っているわけではありません。いや、これはあまりにもクールですが、できる限り-役に立たず、さらに深く掘るのは本当に簡単ではありません。
もちろん、私はstackoverflowに関する質問をしましたが、返事はありませんでした。
onBackpressureBuffer()を非常に迅速に発見したという事実にもかかわらず、この溝には約2週間かかりました(しかし、問題の原因を理解せずに、誰が問題を解決するものを使用しますか?)。

あなたの現在の経験を使用して、仮定します)(observeOnを生成し、サブスクライバ -obortku上で、私の加入者とする場合がありException'yは、彼らがラッパーに蓄積する(契約のために例外だれが16存在することが予想されないようにすることを、1でなければなりません)。 17回目のクリックが発生すると、observeOn()isUnsubscribed()チェックし、それはまだある、誰も入れません。 (しかし、これは私の推測です)。
マジックナンバー16について-一定の大きさである背圧バッファAndroid'a。通常のJavaの場合、 128であり、おそらくこのエラーについて私は決して知らないでしょう。数字の16がアレイのサイズに関係している可能性が高いと推測する価値がありましたが、私は数字の5から始めたので、まったく考えていませんでした。番号16への移行時までに、2 + 2 = 17であることはすでに確信していました。
そして、最も魔法を追加した最後のものはSuppressErrorOperatorです。最初にエラーが無視されなかった場合、すぐにMissingBackpressureExceptionに気付くでしょうそしてその方向に疑問に思いました。数日節約できます。実際には、まだ奇妙なことが残っていますが、SuppressErrorOperatorMissingBackpressureExceptionを含むすべてのエラーを吸収するはずです。なぜならオペレーターはエラーのタイプをチェックしなかったため、すべてが動作し続けるはずです(16回目の試行[サインイン]の後、後続のすべてが常に無駄になることを除く)。

おわりに


したがって、シリーズの最後の部分は終了しました。批判にもかかわらず、実際、私はRxのイディオムが非常に好きです-一度試薬を試してみたら、ローダーやその他とは何の関係もありません。 Netflixのメンバーはよくできています。
ただし、Rxには欠点があります。デビューするのは難しく、一部の演算子は予測できない結果をもたらします。これらの問題を説明する価値はないと思います-記事のフロアはそれについてです。しかし、私は何かを言います。 Rxは興味深いが挑戦的なものです。 Rx脳には多くの程度があります。これは、小さなアクションにのみ使用できます(たとえば、Retrofit呼び出しの結果として))、ただし、Rxでアーキテクチャ全体を構築し、左右の複雑な演算子を使用したり、多数のサブスクリプションを監視したりすることができます。 (かつて、ProducerでBackpressureを使用して画面を切り替えた後、View Stateを復元するコマンドのキューを作成しようとしました。これを試さないことをお勧めします。強く)。一般に、無理をしないと非常にクールになります。
Rxのための情報源を探している人のために、より良いよりも何もありません:すべての事業者との公式ウェブサイト(Ctrlキー+ F、今あなたには、いくつかについてのすべてを知っているスキャン)、RxJava github'e上のwikiの最も初心者のための、および( )オンラインオペレーターのインタラクティブな例
psそして、もしあなたが誰かが最後のキュベットでどんな魔法が起こっているか知っているなら-あなたはコメント、PM、または他の場所で歓迎されています。年末年始よりも詳細を知ってうれしいです。

UPD:突然の約16 stackoverflowの上の質問訪れakarnokd(メインkontribyuterovの1 RxJava当然で述べたように、artemgapchenko)。理由は、observonOn() decople'it演算子がそれ自体の前後にあり、バックプレッシャーバッファーとして機能するためです。なぜなら例外が発生した場合、request()を呼び出さず、データを単に「飲み込む」だけで、observeOn()は最初に要求されたものだけ、つまり定数16を返しますonBackpressureBuffer()は、最初にLong.MAX_VALUEを要求するため、問題を解決しますAkarnokdからの元の回答

Source: https://habr.com/ru/post/J281312/


All Articles