どのような問題を解決していますか?
Androidにとって、非同期操作を実行するタスクが常に最も一般的なタスクの1つであることは周知の事実です。 実際、オフラインでのみ動作するアプリケーションはほとんどなく、ネットワークとの対話なしで実行できるアプリケーションもほとんどありません。 また、データベース、環境設定、通常のファイルなど、デバイスの永続的なメモリにアクセスすることなく、非常に小さな部分が不要になります。 しかし、システム開発の歴史を通じて、「すぐに使える」単一の十分に便利なソリューションは提供されていません。
彼らがどのように問題を解決したか-簡単な歴史
「認証」ボタンをクリックしてタスクを実行するというコンテキストで利用可能なツールを見てみましょう。 実際、何がありますか?
1.標準ストリーム
このコードのすべてが悪いです。 読みにくく、メモリリークが発生し、元に戻せず、画面の回転やAPI呼び出しエラーは処理されません(処理すると、すべてが非常に不便になります)。
2. AsynkTask
もう少し良くなりましたが、それでも十分ではありません。 読み取り可能なエラー処理、キャンセルする機能がありました。 ただし、これまでは、APIへのリクエスト時に画面が回転すると、このコードは正しく機能しません。クラスが定義されているアクティビティへのリンクがリークします。
3.ローダー
GoogleがLoadersを導入したとき、それらは非同期リクエストの特効薬となり、当時のAsyncTaskクラシックを置き換えたように思われました。 残念ながら、奇跡は起こりませんでした。 現時点では、ローダーは非常に不便であることが判明したため、商業プロジェクトでは珍しいゲストです。 このセクションでは、前の2つと同様にコードを引用しません。 代わりに、好奇心の強い読者は、ローダーが必要とするコードの量を評価するために、このテクノロジーの公式ガイドに精通することをお勧めします: developer.android.com/reference/android/content/AsyncTaskLoader.html
4.サービス
サービスは、アプリケーションの使用中にバックグラウンドで「ハング」する長いタスクを実行するのに適しています。 ただし、ここで必要な結果が必要な操作を開始するには、サービスの構造は理想的ではありません。 主に、Intentを介したデータ送信方法によって制限が課されます。これは、まず、限られた量のデータのみを収容し、次に、送信されたデータを何らかの方法でシリアル化できることを必要とします。 人気のあるRobospiceライブラリは 、このテクノロジーで実行されます。
1.標準ストリーム
Button signInButton = (Button) findViewById(R.id.button_auth); signInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { final Activity activity = AuthActivity.this; showProgress(); new Thread(new Runnable() { @Override public void run() { APIFactory.getApi().signIn(); activity.runOnUiThread(new Runnable() { @Override public void run() { goToMainContent(); } }); } }).start(); } });
このコードのすべてが悪いです。 読みにくく、メモリリークが発生し、元に戻せず、画面の回転やAPI呼び出しエラーは処理されません(処理すると、すべてが非常に不便になります)。
2. AsynkTask
Button signInButton = (Button) findViewById(R.id.button_auth); signInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { new AuthTask().execute(); } }); private class AuthTask extends AsyncTask<Void, Void, Boolean>{ @Override protected void onPreExecute() { showProgress(); } @Override protected Boolean doInBackground(final Void... params) { try { APIFactory.getApi().signIn(); }catch (Exception e){ return false; } return true; } @Override protected void onPostExecute(final Boolean result) { if(!isCancelled() && result) { goToMainContent(); } } }
もう少し良くなりましたが、それでも十分ではありません。 読み取り可能なエラー処理、キャンセルする機能がありました。 ただし、これまでは、APIへのリクエスト時に画面が回転すると、このコードは正しく機能しません。クラスが定義されているアクティビティへのリンクがリークします。
3.ローダー
GoogleがLoadersを導入したとき、それらは非同期リクエストの特効薬となり、当時のAsyncTaskクラシックを置き換えたように思われました。 残念ながら、奇跡は起こりませんでした。 現時点では、ローダーは非常に不便であることが判明したため、商業プロジェクトでは珍しいゲストです。 このセクションでは、前の2つと同様にコードを引用しません。 代わりに、好奇心の強い読者は、ローダーが必要とするコードの量を評価するために、このテクノロジーの公式ガイドに精通することをお勧めします: developer.android.com/reference/android/content/AsyncTaskLoader.html
4.サービス
サービスは、アプリケーションの使用中にバックグラウンドで「ハング」する長いタスクを実行するのに適しています。 ただし、ここで必要な結果が必要な操作を開始するには、サービスの構造は理想的ではありません。 主に、Intentを介したデータ送信方法によって制限が課されます。これは、まず、限られた量のデータのみを収容し、次に、送信されたデータを何らかの方法でシリアル化できることを必要とします。 人気のあるRobospiceライブラリは 、このテクノロジーで実行されます。
クロノスは何を提供しますか?
Chronosは、並列スレッドでタスクを完了し、結果または実行エラーをメインスレッドに配信するためのすべての作業を行います。 大まかに言って、このライブラリは、あらゆる種類の長時間の操作のためのコンテナを提供します。
プロジェクトには本格的なwikiがあり、そこからのコードの一部は記事で使用されますが、より完全なガイドについてはgithub.com/RedMadRobot/Chronos/wikiに連絡してください 。
例
Chronosを使用して一般的な問題を解決しましょう。アクティビティでは、何らかのストレージから何らかのオブジェクトを要求する必要があります。このオブジェクトへのアクセスは、UIスレッドで要求を行わないほど長くなります。 最初にコードを記述し、次に何が起こったのかを分析します。
1.最初に、Chronosをプロジェクトに接続する必要があります。 これを行うには、gradleで依存関係を記述するだけです。
compile 'com.redmadrobot:chronos:1.0.5'
2.次に、アクティビティについて説明します。 基本クラスChronosActivityはライブラリのコンポーネントの1つですが、その類似物を簡単に書くことができます。この例はドキュメントにあります。 また、Chronosはフラグメントで使用できます。コードに違いはありません。
class MyActivity extends ChronosActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button startButton = (Button) findViewById(R.id.button_start); startButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { runOperation(new MyOperation()); } }); } public void onOperationFinished(final MyOperation.Result result) { if (result.isSuccessful()) { showData(result.getOutput()); } else { showDataLoadError(result.getError()); } } private void showData(BusinessObject data){ //... } private void showDataLoadError(Exception exception){ //... } }
3.最後に、 MyOperationクラスのデータを取得するためのビジネスロジックについて説明します。
class MyOperation extends ChronosOperation<BusinessObject> { @Nullable @Override public BusinessObject run() { final BusinessObject result ; // here you should write what you do to get the BusinessObject return result; } @NonNull @Override public Class<? extends ChronosOperationResult<BusinessObject>> getResultClass(){ return Result.class; } public final static class Result extends ChronosOperationResult<BusinessObject> { } }
実際、それがすべてです。 このコードで何が起こっているのかを詳しく見てみましょう。 最初から始めましょう。
UIクラスのカスタマイズ
class MyActivity extends ChronosActivity {
Chronosを使用するには、Acvitityまたはフラグメントの基本クラスがライブラリで提案されているものを継承するか、ライフサイクルメソッドに特定のコードを含める必要があります 。例はドキュメントに記載されています 。
運転開始
runOperation(new MyOperation());
ここでは、ChronosActivityクラスの基本メソッドが呼び出され、新しく作成された操作が転送されます。 このメソッドを呼び出した直後に、Chronosは操作をキューに入れ、並列スレッドで実行を開始します。
操作の結果の処理
public void onOperationFinished(final MyOperation.Result result) { if (result.isSuccessful()) { showData(result.getOutput()); } else { showDataLoadError(result.getError()); } }
このメソッドは、操作の完了後に呼び出されるか、実行中に例外がスローされます。 このようなハンドラーメソッドには、署名public void onOperationFinished(ResultType)が必要です。 重要な点:メソッドはonResume()呼び出しとonPause()呼び出しの間でのみ呼び出されます。つまり、その時点で既に無効になっていることを恐れることなく、そのUIを簡単に変更できます。 さらに、アクティビティがターン、バックグラウンドへの移行、またはその他の理由で再作成された場合-クロノスはいずれの場合でも結果を返します(唯一の例外は、この場合、OutOfMemoryクロノスが古い結果データを消去できないようにするためにシステムがメモリ不足になることです)。
「電話はどこから来ますか?」
注意深い読者は、Activityが特定のインターフェイスを実装していないことに気付くでしょう。正確にこのメソッドはどこから来たのでしょうか? 答えは、リフレクションを含むコードからです。 インターフェイスの代わりにリフレクションを実行するという決定は、JavaのTypeErasureにより行われました。これにより、同じテンプレートインターフェイスを異なるパラメーターで同時に実装することができなくなります。 つまり、これは、1つのアクティビティで、必要な種類の操作の結果を処理できるようにするために行われます。
操作クラスの設定
class MyOperation extends ChronosOperation<BusinessObject> {
ChronosOperationクラスは、特定のタイプのオブジェクト(この場合はBusinessObject)を取得するビジネスロジックをカプセル化します。 すべてのユーザー操作はChronosOperationから継承する必要があります。
ビジネスロジック
@Nullable @Override public BusinessObject run() { final BusinessObject result ; // here you should write what you do to get the BusinessObject return result; }
ChronosOperationクラスのこの抽象メソッドは、実際には、オブジェクトを取得するビジネスロジックを担当します。 並列スレッドで実行されるため、その中で任意の長いアクションを実行できます。これにより、アプリケーションインターフェイスで遅延が発生することはありません。 また、スローされた例外は、アプリケーションをクラッシュさせることなく、呼び出し元のオブジェクトに慎重に渡されます。
結果の命名
@NonNull @Override public Class<? extends ChronosOperationResult<BusinessObject>> getResultClass(){ return Result.class; } public final static class Result extends ChronosOperationResult<BusinessObject> { }
次のメソッドとクラスは、 onOperationFinishedメソッドのパラメータタイプとしてクラスを指定して、アクティビティコードが特定の操作ごとに結果ハンドラーを指定できるように設計されています。 結果を同じ方法で処理する場合は、異なる操作に同じ結果クラスを使用できます。
要約すると、Chronosでの作業に必要なコードセクションの最小セットを収集します。
- 操作クラス
- UIオブジェクトの操作呼び出しコード
- UIオブジェクトの結果処理コード
他に何がありますか?
- 画面を回転したときに既に実行中のクエリを失わないように、Chronos は操作の起動に「名前を付ける」機能を提供します 。
- 操作をキャンセルすることは可能です。
- 操作の結果を、それを引き起こしたオブジェクトだけでなく、サブスクライブしているすべてのオブジェクトも受信したい場合は、ブロードキャストを起動できます 。
- さらに、必要に応じて、Chronosクラスを使用して操作を同期的に実行できます。
それでは、なぜクロノスを使用できるのですか?
- Chronosはスレッド間のデータ転送を処理し、ビジネスロジックのみを心配します。
- Chronosは、アクティビティライフサイクルとフラグメントのすべてのニュアンスを考慮し、処理の準備ができたときにのみ結果を提供し、それまでデータを保存します。
- Chronosはメモリをリークしません。 あまりにも多くのActivityオブジェクトがリークしたため、クラッシュをキャッチするリスクはもうありません。
- Chronosは単体テストでカバーされています。
- 最後に、Chronosはオープンソースプロジェクトです。 いつでもコードを取得して、ニーズに合わせて書き換えることができます。 テストのおかげで、コードの変更を簡単に検証できます。
GitHubのプロジェクトにリンクします 。 そこには、完全なライブラリガイド、使用例、そしてもちろんソースコードがあります。
また読む:
ダイエット中の植物コントローラー:Android
モバイルアプリケーションのアーキテクチャ設計:パート1
モバイルアプリケーションのアーキテクチャ設計:パート2