今日、Androidアプリケーションでアクティビティとサービスの相互作用を整理する方法を説明することにしました。 このトピックの動機は、たとえば、サーバーへの旅行がAsyncTaskのアクティビティ内で編成されているアプリケーションを見つけることができるという事実です。 同時に、多くの場合、これはサービスで行われるべきであるが、オフィスでは行われないという真の考えがあります。 ドキュメントは、それらの間の双方向の相互作用の正しいアーキテクチャの組織についての言葉ではありません。
そのため、試行錯誤を繰り返して、自分に必要なすべての問題を個人的にカバーするアーキテクチャに到達しました。
この方法については後で説明します。
空撮
最初に、提案されたアーキテクチャの概要を見てみましょう。

この記事の後半では、管理されたフィードバックと管理されていないフィードバックという2つの用語を使用します。 これらは非公式の用語ですが、私はそれらが好きなので私はそれらを使用します。 管理対象-これらは、Androidプラットフォームによって提供される通知です(ContentProvider + ContentObserverシステム)。 UIが管理された通知を受信するために、正しく実装されたプロバイダー以外は必要ありません。
さらに興味深いのは、管理されていない通知の実装方法、つまり、イベントシステムを使用して実装される通知です。 結局、プロバイダーへの書き込みに関連付けられているのは、サービス内の一部の操作のパフォーマンスだけではないため、サービスが完了したことをクライアントに通知する独自のメカニズムが必要です。
したがって、このアーキテクチャは、システムの4つの主要コンポーネントの存在を意味します。
- 標準インターフェースマッピングロールとして機能するアクティビティ
- サービス-バックグラウンドスレッドで大量の作業を行うサービス
- ServiceHelper-アクティビティとサービスを結び付け、制御されない通知を提供するコンポーネント
- ContentProviderは、管理通知を実装するのに役立つUIに依存するオプションのコンポーネントです。
サービス
当社のサービスはコマンドプロセッサとして機能します。
着信インテントごとに追加機能があります。
- 実行するアクション
- コマンドで定義された引数
- ResultReceiver
サービスは渡されたアクションを見て、実行する必要があるコマンドにそれをマップし、引数とResultReceiverをコマンドに渡します。
サービスを実装する最も簡単なオプション:
protected void onHandleIntent(Intent intent) { String action = intent.getAction(); if (!TextUtils.isEmpty(action)) { final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER); if (AwesomeHandler.ACTION_AWESOME_ACTION.equals(action)) { new AwesomeHandler().execute(intent, getApplicationContext(), receiver); } // ..... } }
ここで、大きなifブロックでは、目的のコマンドが単に検索されます。 もちろん、ここではifaを回避するために好きなように運転できます:Mapアクションハンドラーを保持する、ファクトリーを作成する、IoCを使用するなどが、これは記事の範囲外です。
ハンドラー
ハンドラーは、実行可能プロシージャーをカプセル化します。 私にとって、それらは特定の階層を形成し、基本クラスは次のようになります。
public abstract class BaseIntentHandler { public static final int SUCCESS_RESPONSE = 0; public static final int FAILURE_RESPONSE = 1; public final void execute(Intent intent, Context context, ResultReceiver callback) { this.callback = callback; doExecute(intent, context, callback); } public abstract void doExecute(Intent intent, Context context, ResultReceiver callback); private ResultReceiver callback; private int result; public int getResult() { return result; } protected void sendUpdate(int resultCode, Bundle data) { result = resultCode; if (callback != null) { callback.send(resultCode, data); } } }
次の階層レベルでは、httpリクエストを準備する基本的なコマンドを実装しましたが、これもまた記事の範囲外です。 一般に、ベースコマンドを継承してdoExecuteを実装します。必要に応じて、sendUpdateメソッドを呼び出し、コード(成功/エラー)とデータ付きバンドルを渡します。
ServiceHelper
ServiceHelperは、UIとサービスの間の中間層であり、UIのサービス呼び出しを簡素化し、インテントをパッケージ化するためのルーチン操作を実行します。 また、サービスからの応答を調整し、現在実行中のコマンドに関する情報が含まれています。
それではどのように機能しますか:
- UIはヘルパーメソッドを呼び出し、ヘルパーはリクエストIDを返します
- ヘルパーはリクエストIDを記憶します
- ResultReceiverが貢献するインテントを収集し、サービスに送信します
- サービスが操作を完了すると、すべてのリスニングUIコンポーネントがonReceiveResultで通知されます
コードを見てみましょう:
public class ServiceHelper { private ArrayList<ServiceCallbackListener> currentListeners = new ArrayList<ServiceCallbackListener>(); private AtomicInteger idCounter = new AtomicInteger(); private SparseArray<Intent> pendingActivities = new SparseArray<Intent>(); private Application application; ServiceHelper(Application app) { this.application = app; } public void addListener(ServiceCallbackListener currentListener) { currentListeners.add(currentListener); } public void removeListener(ServiceCallbackListener currentListener) { currentListeners.remove(currentListener); } // .....
ヘルパーサービスは、サブスクライバーのリストを配列に保持します。チームの作業に関する通知が送信されるのはこのリストです。
private Intent createIntent(final Context context, String actionLogin, final int requestId) { Intent i = new Intent(context, WorkerService.class); i.setAction(actionLogin); i.putExtra(WorkerService.EXTRA_STATUS_RECEIVER, new ResultReceiver(new Handler()) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { Intent originalIntent = pendingActivities.get(requestId); if (isPending(requestId)) { pendingActivities.remove(requestId); for (ServiceCallbackListener currentListener : currentListeners) { if (currentListener != null) { currentListener.onServiceCallback(requestId, originalIntent, resultCode, resultData); } } } } }); return i; }
これは、サービスに送信するインテントを作成するための一般的な方法です。
さらに興味深い場所はpendingActivitiesです。これは、サービスで現在実行中のすべてのタスクの登録です。 ServiceHelperメソッドを呼び出すとIDが取得されるため、コマンドが実行されているかどうかを常に確認できます。 詳細については、以下の記事をご覧ください。
public boolean isPending(int requestId) { return pendingActivities.get(requestId) != null; } private int createId() { return idCounter.getAndIncrement(); } private int runRequest(final int requestId, Intent i) { pendingActivities.append(requestId, i); application.startService(i); return requestId; }
次に、サービスに対して何らかのアクションを実行するパブリックメソッドの例を示します。
public int doAwesomeAction(long personId) { final int requestId = createId(); Intent i = createIntent(application, AwesomeHandler.ACTION_AWESOME_ACTION, requestId); i.putExtra(AwesomeHandler.EXTRA_PERSON_ID, personId); return runRequest(requestId, i); }
そして、そのような方法を突き出すのは、サービスがサポートするのとまったく同じ数のチームになります。
アクティビティ
だから、私が言ったように、私たちはインターフェースを持っています:
public interface ServiceCallbackListener { void onServiceCallback(int requestId, Intent requestIntent, int resultCode, Bundle data); }
基本的な抽象アクティビティでこのインターフェイスを実装すると便利だと思います。 次に、特定のアクティビティで、onServiceCallbackメソッドを再定義するだけで通知を受信できます。これは、アクティビティの標準のコールバックメソッドに非常に似ています。つまり、クライアントコードに適切に適合します。
public abstract class AwesomeBaseActivity extends FragmentActivity implements ServiceCallbackListener { private ServiceHelper serviceHelper; protected AwesomeApplication getApp() { return (AwesomeApplication ) getApplication(); } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); serviceHelper = getApp().getServiceHelper(); } protected void onResume() { super.onResume(); serviceHelper.addListener(this); } protected void onPause() { super.onPause(); serviceHelper.removeListener(this); } public ServiceHelper getServiceHelper() { return serviceHelper; } public void onServiceCallback(int requestId, Intent requestIntent, int resultCode, Bundle resultData) { } }
アクティビティがonResume / onPauseメソッドでServiceHelperをサブスクライブおよびサブスクライブ解除する方法に注目してください。 これにより、画面を回転したり、アプリケーションを最小化するなど、アクティビティを再作成する際の問題を回避できます。
onServiceCallbackで何が起こるか見てみましょう:
- requestId-リクエストの送信時に生成される一意の識別子
- requestIntent-送信した元のインテント
- resultCode-実行結果コード
- resultData-データ
これで、アクティビティでボイラープレートコードのヒープなしで常にサービスから通知を受信できるように、必要なものがすべて揃いました。
さらに、これは非常に便利に思えますが、特定のリクエストをIDで識別し、同じタイプのすべてのリクエストをアクションで識別できるため、柔軟性が非常に高くなります。
class AwesomeActivity extends AwesomeBaseActivity { private int superRequestId; ... private void myMethod() { superRequestId = getServiceHelper().doAwesomeAction(); } public void onServiceCallback(int requestId, Intent requestIntent, int resultCode, Bundle resultData) { if (AwesomeHandler.ACTION_AWESOME_ACTION.equals(requestIntent.getAction()) { // } if (requestId == superRequestId) { // } } }
また、リクエストIDがあれば、保留中のチェックを実行できます。 シーケンスを想像してください:
- ユーザーがアクションを開始し、スピナーが開始しました
- 2分間アプリケーションを閉じた
- アクションはすでに完了しています
- ユーザーが再び開いた
- ここで、操作が完了したかどうかをonResumeで確認し、ツイストを削除します
つまり、必要に応じてgetServiceHelper()。isPending(requestId)を呼び出すだけです。
おわりに
おそらくそれだけです。
このアーキテクチャの普遍性や独自性を装っていないことをすぐに言わなければなりません。 それは試行錯誤、さまざまな資料の見方などによってゆっくりと削除されました。しかし、これまでのところ、実際の商業プロジェクトに対するすべてのニーズを100%カバーしています。
さらに、何かが足りない場合、簡単に拡張できます。 明らかなことから:
- 成功と失敗に加えて進行コードを追加すると、サービスからタスクの進行に関する情報を送信し、ProgressBarなどに表示することができます
- コードを固定してタスクを中断する
- など
別の詳細、ServiceHelperコードは同期されません。これは、そのメソッドが常にUIスレッドで呼び出されることが理解されているためです。 これが当てはまらない場合は、ServiceHelperのステータスの変更に同期を追加する必要があります 。
一般的に、あなたの注意をありがとう、誰かが助けることを願っています。 コメントで質問やコメントに答える準備ができました。
UPD :GitHubのアーキテクチャを示す小さなサンドボックスの例を投稿しました: https : //github.com/TheHiddenDuck/android-service-arch
UPD 2 :スレッドプールで実行されるサービスを実装する例を追加