20024


現在アプリでVolleyを使用していて、それ関連は`extends Applicationしたクラス`にまとめてあります。  
この度Google
Analyicsを導入することになり[公式](https://developers.google.com/analytics/devguides/collection/android/v4/)を参考に取り組んでいるのですが、それ用の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宣言は消しています。)

[code]

    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)));
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2015-12-10T10:03:46.493
19851
2015-12-15T11:47:56.900
2015-12-15T11:47:56.900
7256
9710
question
android google-analytics-api
すでにApplicationを継承したクラスがある場合に、Google Analytics関連のメソッドなどを追加でどう定義するか


AppControllerがシングルトン設計なので`AppController.getInstance()`でContextを取得できる  
ということを忘れていました。  
なので

  4. Google Analytics関連のユーティリティクラスを作る  
Tracker取得に用いるContextはAppControllerのインスタンスを用いる。

という設計にしようと思います。

[code]

    /**
     * Google Analytics関連のメソッドをまとめたクラス
     */
    public class GoogleAnalyticsUtility {
        private static Tracker sTracker = null;
    
        /**
         * Google Analytics用トラッカーを取得
         * @return tracker
         */
        public static Tracker getDefaultTracker(){
            if (sTracker == null) {
                GoogleAnalytics analytics = GoogleAnalytics.getInstance(AppController.getInstance());
                sTracker = analytics.newTracker(R.xml.global_tracker);
            }
            return sTracker;
        }
    
        //以下略
    }
    
[/code]


CC BY-SA 3.0
2015-12-15T07:13:52.090
20015
2015-12-15T07:13:52.090
9710
19851
answer


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

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

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

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

* * *

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


CC BY-SA 3.0
2015-12-15T11:41:02.520
20024
2015-12-15T11:41:02.520
5337
19851
answer