すでにApplicationを継承したクラスがある場合に、Google Analytics関連のメソッドなどを追加でどう定義するか

投稿者: Anonymous

現在アプリでVolleyを使用していて、それ関連はextends Applicationしたクラスにまとめてあります。
この度Google Analyicsを導入することになり公式を参考に取り組んでいるのですが、それ用のTrackerの取得をVolley同様extends Applicationしたクラスに書くよう書かれています。
Volley関連はシングルトンがよく、Analytics関連はContextが使えるシングルトンがよいからどちらもextends Applicationしたクラスに定義することを望んているのだと思いますが、そのクラスは2つも作れないと思います。

実際に私が定義しているVolley関連のクラスにはContextを使用しているコードはなさそうだったので、サンプルによくあるコードのようにextends Applicationしたクラスにしなくても普通のシングルトンでよかったかもしれませんが、今更の変更はテスターの負担増になるので避けたいと思っています。
ですが、Volley関連とAnalytics関連を同一ではなく別クラスで管理したいと考えています。

そこでAnalytics関連のクラスの設計について以下の3パターンを考えたのですが、どれが良いでしょうか?
もしくは他の設計の方が良いでしょうか?

  1. 普通のシングルトンで定義してTrackerを取得するメソッドの引数にContextを渡す
    公式のサンプルのような同一のトラッカーを使い回すことを考えると、2回目以降は無駄にContextを渡していることになる。
  2. Volley関連のクラスのみでインスタンスを生成するようにした擬似シングルトンで定義する
    生成時にContextを渡してTrackerを作るので無駄にContextを渡すなんてことはないが、設計としていまいちでは。
  3. Volley関連のクラスをextendsしたクラスを定義する
    そうすればextends Applicationと同義にはなるとは思うが、このような設計はやってはいけない気がする。

追記

現在のApplicationクラスの実装は次のようになっています。
(package宣言は消しています。)


import android.app.Application;
import android.graphics.Bitmap;
import android.util.LruCache;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

import de.greenrobot.event.EventBus;

/**
 * キューでリクエストを管理するためのクラス.
* * Applicationを拡張して作ったシングルトンクラス。 * * @see android.app.Application Application */ public class AppController extends Application { public static final String TAG = AppController.class.getSimpleName(); private RequestQueue mRequestQueue; private ImageLoader mImageLoader; private static AppController sInstance; /** * AndroidManifest.xmlのandroid:name項目によりインスタンスが作成されて呼び出される. */ @Override public void onCreate() { super.onCreate(); sInstance = this; } /** * シングルトロンクラスのためのインスタンス取得用メソッド. * * @return このクラスのインスタンス */ public static synchronized AppController getInstance() { return sInstance; } /** * リクエストキューを取得するためのメソッド. * * @return リクエストキュー */ private RequestQueue getRequestQueue() { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } return mRequestQueue; } /** * イメージローダーを取得するためのメソッド. * * @return イメージローダー */ public ImageLoader getImageLoader() { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } if (mImageLoader == null) { mImageLoader = new ImageLoader(mRequestQueue, new ImageLruCache()); } return mImageLoader; } /** * 画像をキャッシュするためのクラス. */ public static class ImageLruCache implements ImageLoader.ImageCache { private LruCache mMemoryCache; public ImageLruCache() { int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; mMemoryCache = new LruCache(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; } }; } @Override public Bitmap getBitmap(String url) { return mMemoryCache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { mMemoryCache.put(url, bitmap); } } /** * リクエストをキューに追加するためのメソッド. * * @param req 行う処理を定義したリクエスト * @param */ public void addToRequestQueue(Request req) { req.setTag(TAG); getRequestQueue().add(req); } /** * 保留中のリクエストをキャンセルするためのメソッド. * * @param tag キャンセルしたい保留中のリクエストのタグ */ public void cancelPendingRequests(Object tag) { if (mRequestQueue != null) { mRequestQueue.cancelAll(tag); } } /** * Volleyのエラー時に呼び出すメソッド. * * @param error Volleyのエラー内容 */ public void onErrorResponse(VolleyError error) { if (error.networkResponse == null) { EventBus.getDefault().post(new UserEvent(UserEvent.Id.CONNECTION_ERROR, "")); } else { EventBus.getDefault().post(new UserEvent(UserEvent.Id.CONNECTION_ERROR, String.valueOf(error.networkResponse.statusCode))); } } }

解決

この場合、設計として良いのはカスタムApplicationクラスのフィールドとして、RequestQueueおよびTrackerを保持する実装とすることです。

なぜApplicationのサブクラスに保持すべきだと書かれている(4.のような単純なシングルトンが好まれない)のかというと、同じアプリケーションでも動作するActivityServiceのプロセスが単一だとは限らないからです。

他アプリやAlarmManagerなどからActivityが起動されたとき、もともと存在していたActivityとは異なるプロセスで起動する場合があります。このとき、異なるVM上で動作しているそれぞれのActivityがプロセス境界を越えてシングルトンを共有することはできません。(それぞれのVM上でRequestQueueTrackerが生成される、シングルトンが分裂する状態になります)

また一般に、RequestQueueTrackerが有効であるべきスコープと、Applicationの生存期間が等しいというのも利点です。一般的なアプリケーションのテストという観点で考えると、遅延初期化されるシングルトンよりも、Applicationが必ずそれぞれ1つだけのRequestQueueTrackerと共に存在する状況の方は好ましいと思います。


なお、シングルトンとして正しく実装するには、synchronizedキーワードで排他制御を行う必要があります。複数のスレッドからgetInstance()された場合に、多重にインスタンスを生成してしまう可能性があるからです。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *