Androidアプリケーションアーキテクチャ

標準アクティビティおよびAsyncTaskからRxJavaを使用した最新のMVPアーキテクチャへの旅。









プロジェクトコードは、相互に機能するメカニズムとして機能する独立したモジュールに分割する必要があります( Chester Alvarezの写真)。



Android用開発ツールのエコシステムは非常に高速に開発されています。 毎週、誰かが新しいツールを作成し、既存のライブラリを更新し、新しい記事を書き、プレゼンテーションを行います。 1か月間休暇を取り、帰国するまでに、サポートライブラリおよび/またはGoogle Play開発者サービスの最新バージョンが既に公開されています。



過去3年間、 リボット Androidアプリケーションを開発してきましたが、アプリケーションのアーキテクチャと使用するテクノロジーの両方が常に進化し、改善されてきました。 この記事では、私たちが学んだ道、私たちが学んだ教訓、私たちが犯した間違い、およびこれらすべてのアーキテクチャの変更につながった推論を示します。



古き良き時代



2012年、プロジェクトの構造は非常にシンプルに見えました。 ネットワークライブラリはありませんでしたが、 AsyncTask



は引き続き友人でした。 以下の図は、これらのソリューションのアーキテクチャの例を示しています。









コードは2つのレベルに分割されました。RESTAPIとさまざまなローカルリポジトリの両方を介して受信したデータの受信/保存を担当するデータレイヤーと、データの処理と表示を担当するビューレイヤーです。



APIProvider



は、アクティビティとフラグメントがREST APIと対話できるようにするメソッドを提供します。 これらのメソッドは、 URLConnection



AsyncTask



を使用してバックグラウンドスレッドで要求を実行し、コールバック関数を介して結果をアクティビティに配信します。 CacheProvider



も同じCacheProvider



機能しますCacheProvider



またはSQLiteからデータを取得するメソッドがあり、結果を返すコールバック関数があります。



問題



このアプローチの主な問題は、プレゼンテーションのレベルの責任が大きすぎることです。 アプリケーションがブログ投稿のリストをダウンロードし、それらをSQLiteにキャッシュしてからListView



表示する必要がある簡単なシナリオを想像してみましょう。 Activity



は次のことを行う必要があります。



  1. APIProvider#loadPosts(Callback)



    メソッドを呼び出します。
  2. 渡されたCallback



    onSuccess()



    メソッドがonSuccess()



    を待ってから、 onSuccess()



    CacheProvider#savePosts(Callback)



    呼び出します。
  3. 渡されたCallback



    onSuccess()



    メソッドがonSuccess()



    を待ってから、データをListView



    表示します。
  4. APIProvider



    CacheProvider



    両方で発生する可能性のある2つのエラーを個別に処理します。


そして、これは別の簡単な例です。 実際には、APIが、プレゼンテーションレベルが期待する形式ではないデータを返す場合があります。つまり、 Activity



は、データを操作する前に何らかの方法でデータを変換および/またはフィルター処理する必要があります。 または、たとえば、 loadPosts()



はどこかから受け取る必要がある引数(たとえば、Play Services SDKを介して要求するメールアドレスloadPosts()



を取ります。 確かに、SDKはコールバック関数を介して非同期的にアドレスを返します。これは、コールバック関数の3つのレベルのネストがあることを意味します。 ますます複雑になっていくと、コールバック地獄と呼ばれるものになってしまいます。



要約:





RxJavaを使用した新しいアーキテクチャ



上記のアプローチを2年間使用しました。 この間に、私たちはいくつかの変更を行い、説明した問題による痛みと苦痛を軽減しました。 たとえば、いくつかの補助クラスを追加し、それらのロジックの一部を取り出してAPIProvider



とフラグメントをアンロードし、 APIProvider



Volleyの使用を開始しました。 これらの変更にもかかわらず、コードのテストは依然として困難であり、コールバックヘルがときどき発生しました。



RxJavaに関するいくつかの記事を読んだ2014年に状況は変わり始めました。 いくつかの試用プロジェクトで試してみたところ、ネストされたコールバック関数の問題の解決策が見つかったようです。 リアクティブプログラミングに慣れていない場合は、 この概要を読むことをお勧めします。 要するに、RxJavaでは、非同期ストリーム(データの管理者:この場合、ストリームのようなストリームを意味し、スレッド-実行スレッドと混同しないでください)を使用して、変換するストリームに適用できる多くの演算子を提供します。必要に応じてデータをフィルタリングまたは結合します。



過去2年間に蓄積したすべてのバンプを考慮して、新しいアプリケーションのアーキテクチャを検討し始め、次のことに気付きました。









コードはまだ2つのレベルに分かれています。データレイヤーにはDataManager



と一連のヘルパークラスが含まれ、プレゼンテーションレイヤーにはActivity



Fragment



ViewGroup



などのAndroid SDKクラスが含まれます。



ヘルパークラス(図の3列目)の責任範囲は非常に限定されており、一貫した方法で実装されています。 たとえば、ほとんどのプロジェクトには、REST APIにアクセスしたり、データベースからデータを読み込んだり、サードパーティのSDKとやり取りしたりするためのクラスがあります。 アプリケーションごとにヘルパークラスのセットが異なりますが、最も一般的に使用されるのは次のとおりです。





多くのパブリックヘルパークラスメソッドは、RxJava Observables



返します。



DataManager



は、新しいアーキテクチャの中心部分です。 RxJavaオペレーターを広範囲に使用して、ヘルパーから受け取ったデータを結合、フィルター処理、変換します。 DataManager



のタスクは、アクティビティとフラグメントをデータの「コーミング」作業から解放することです。 DataManager



は、必要なすべての変換を実行し、表示可能なデータを提供します。



以下のコードは、 DataManager



メソッドがどのように見えるかを示しています。 次のように機能します。



  1. Retrofitを介して投稿のリストを読み込みます。
  2. DatabaseHelper



    を介してローカルデータベースにデータをキャッシュします。
  3. 投稿をフィルターし、今日公開されたものを選択します。これは、プレゼンテーションレベルでのみ表示されるためです。


 public Observable<Post> loadTodayPosts() { return mRetrofitService.loadPosts() .concatMap(new Func1<List<Post>, Observable<Post>>() { @Override public Observable<Post> call(List<Post> apiPosts) { return mDatabaseHelper.savePosts(apiPosts); } }) .filter(new Func1<Post, Boolean>() { @Override public Boolean call(Post post) { return isToday(post.date); } }); }
      
      





プレゼンテーションレベルのコンポーネントは、このメソッドを呼び出し、それによって返されるObservable



をサブスクライブするだけです。 サブスクリプションが完了すると、受信したObservable



によって返された投稿をAdapter



に追加して、 RecyclerView



または同様の表示を行うことができます。



このアーキテクチャの最後の要素はイベントバスです。 イベントバスを使用すると、データレベルで発生する特定のイベントに関するメッセージを実行でき、プレゼンテーションレベルのコンポーネントはこれらのメッセージをサブスクライブできます。 たとえば、 DataManager



signOut()



メソッドは、対応するObservable



作業Observable



完了したことを通知するメッセージをトリガーし、このイベントにサブスクライブしたアクティビティは、ユーザーがログアウトしていることを示すためにインターフェースを再描画できます。



このアプローチはどのように優れていますか?





どのような問題が残っていましたか?







モデルビュープレゼンターを試す



過去1年間で、 MVPまたはMVVMとして、個々のアーキテクチャパターンがAndroidコミュニティで人気を集め始めています。 別の記事と同様に、 テストプロジェクトでこれらのパターンを調べた結果、MVPがプロジェクトのアーキテクチャに大幅な変更を加えることができることがわかりました。 すでにコードを2つのレベル(データとプレゼンテーション)に分割しているため、MVPの導入は自然に見えました。 新しいレベルのプレゼンターを追加し、コードの一部をビューからそこに転送する必要がありました。









データレベルは変更されませんが、MVPの対応するレベルの名前と一致するモデルと呼ばれるようになりました。



プレゼンターは、モデルからデータをロードし、データがロードされたときにプレゼンテーションレベルで適切なメソッドを呼び出す責任があります。 プレゼンターは、 DataManager



によって返されるObservables



にサブスクライブします。 したがって、 サブスクリプションスケジューラなどのエンティティを使用する必要があります。 さらに、発生したエラーを分析したり、必要に応じて追加の演算子をデータストリームに適用したりできます。 たとえば、一部のデータを除外する必要があり、このフィルターが他のどこでも使用されない可能性が高い場合、このフィルターをDataManager



ではなくプレゼンターレベルに移動することは理にかなっています。



以下は、プレゼンターレベルのメソッドの1つです。 前のセクションで定義したdataManager.loadTodayPosts()



メソッドによって返されるObservable



へのサブスクリプションがあります。



 public void loadTodayPosts() { mMvpView.showProgressIndicator(true); mSubscription = mDataManager.loadTodayPosts().toList() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new Subscriber<List<Post>>() { @Override public void onCompleted() { mMvpView.showProgressIndicator(false); } @Override public void onError(Throwable e) { mMvpView.showProgressIndicator(false); mMvpView.showError(); } @Override public void onNext(List<Post> postsList) { mMvpView.showPosts(postsList); } }); }
      
      





mMvpView



は、 mMvpView



が使用するmMvpView



レベルのコンポーネントです。 通常、 Activity



Fragment



またはViewGroup



ます。



前のアーキテクチャと同様に、プレゼンテーションレイヤーにはAndroid SDKの標準コンポーネントが含まれています。 違いは、これらのコンポーネントがObservables



直接サブスクライブしないことです。 代わりに、 MvpView



インターフェースを実装し、 showError()



showProgressIndicator()



などの明確で理解可能なメソッドのリストを提供します。 プレゼンテーションレベルのコンポーネントは、ユーザーの操作(クリックイベントなど)を処理し、プレゼンターで適切なメソッドを呼び出す役割も担います。 たとえば、投稿のリストを読み込むボタンがある場合、 Activity



OnClickListener



presenter.loadTodayPosts()



メソッドをOnClickListener



ます。



実際の例をご覧になりたい場合は、Githubのリポジトリをご覧ください 。 さらに、必要な場合は、 アーキテクチャの構築に関する推奨事項をご覧ください。


このアプローチはどのように優れていますか?





どのような問題が残っていましたか?








私が説明したアプローチは理想的ではないことに言及することが重要です。 一般的に、あなたのすべての問題をきっぱりと解決する非常にユニークでユニークなアーキテクチャがどこかにあると信じるのは単純です。 Androidエコシステムは今後も高速で進化し続けるため、イベント、研究、読書、実験に遅れずについていく必要があります。 なんで? 素晴らしいAndroidアプリを作り続けるために。



私の記事を楽しんで、それが役に立つことを願っています。 その場合は、[推奨]ボタンをクリックすることを忘れないでください(翻訳者:元の記事に移動し、記事の最後にあるハートボタンをクリックしてください)。 また、現在のアプローチについてのご意見をお聞かせください。



All Articles