Androidシングルトンパターンの実装戦略と最適化

基本概念と実装手法

シングルトンの必須特性

  • 単一インスタンス保証:クラスのインスタンスが1つしか存在しない
  • 統一アクセスポイント:グローバルな取得メソッドを提供
  • 自己初期化:クラス自身がインスタンス管理を担当

基本実装手法の比較

即時初期化(Immediate Initialization)

public class ImmediateInstance {
    private static final ImmediateInstance INSTANCE = new ImmediateInstance();
    
    private ImmediateInstance() {}
    
    public static ImmediateInstance getSingleton() {
        return INSTANCE;
    }
}

特徴
- JVMのクラスローディングでスレッドセーフ
- アプリ起動時初期化(リソース消費の可能性)
- 実装がシンプル

遅延初期化(Lazy Initialization)

public class LazyInstance {
    private static LazyInstance instance;
    
    private LazyInstance() {}
    
    public static LazyInstance getSingleton() {
        if (instance == null) {
            synchronized (LazyInstance.class) {
                if (instance == null) {
                    instance = new LazyInstance();
                }
            }
        }
        return instance;
    }
}

特徴
- 初回アクセス時にインスタンス生成
- 同期処理によるパフォーマンス影響
- 基本実装ではスレッドセーフ性の課題

Android向け最適化実装

二重チェックロック(DCL)の改良版

class SafeInstance private constructor() {
    companion object {
        @Volatile private var instance: SafeInstance? = null
        
        fun getInstance(context: Context): SafeInstance {
            return instance ?: synchronized(this) {
                instance ?: SafeInstance().apply {
                    initialize(context.applicationContext)
                }
            }
        }
        
        private fun initialize(appContext: Context) {
            // アプリケーションコンテキストでの初期化
        }
    }
}

利点
- スレッドセーフ性を確保
- 初回アクセス時の遅延ロード
- 最初の初期化時のみロックを取得

静的内部クラスパターン(推奨)

public class HolderPattern {
    private HolderPattern() {}
    
    private static class Holder {
        static final HolderPattern INSTANCE = new HolderPattern();
    }
    
    public static HolderPattern getSingleton() {
        return Holder.INSTANCE;
    }
}

動作原理
- JVMのクラスローディングメカニズムでスレッドセーフを保証
- getInstance()呼び出し時にHolderクラスがロードされる

Android特有の利用事例

グローバル設定管理

object GlobalConfig {
    private lateinit var appContext: Context
    
    fun setup(context: Context) {
        appContext = context.applicationContext
    }
    
    val apiEndpoint: String
        get() = BuildConfig.API_BASE
    
    fun getDeviceIdentifier(): String {
        return Settings.Secure.getString(
            appContext.contentResolver,
            Settings.Secure.ANDROID_ID
        )
    }
}

// アプリケーション初期化
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        GlobalConfig.setup(this)
    }
}

ネットワーククライアント管理

public class NetworkClient {
    private static Retrofit client;
    private static final String API_URL = "https://api.example.com";
    
    private NetworkClient() {}
    
    public static Retrofit getRetrofit() {
        if (client == null) {
            synchronized (NetworkClient.class) {
                if (client == null) {
                    client = new Retrofit.Builder()
                        .baseUrl(API_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
                }
            }
        }
        return client;
    }
}

メモリ管理とパフォーマンス最適化

ライフサイクル管理の重要性

Activityコンテキストを保持するとメモリリークのリスクが発生するため、Applicationコンテキストを使用する必要がある。

マルチプロセス対応実装

public class MultiProcessSafe {
    private static MultiProcessSafe instance;
    
    public static MultiProcessSafe getInstance(Context context) {
        if (instance == null) {
            synchronized (MultiProcessSafe.class) {
                if (instance == null && isMainProcess(context)) {
                    instance = new MultiProcessSafe();
                }
            }
        }
        return instance;
    }
    
    private static boolean isMainProcess(Context context) {
        String processName = context.getPackageName();
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
        for (ActivityManager.RunningAppProcessInfo info : am.getRunningAppProcesses()) {
            if (info.pid == pid && processName.equals(info.processName)) {
                return true;
            }
        }
        return false;
    }
}

実践的な問題解決

システムサービスでのシングルトン利用の理由

  1. リソース制御
    InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
  2. Binder接続管理:1回の接続で済む
  3. パフォーマンス最適化:重いオブジェクトの重複生成を防ぐ

メモリリーク対策

  1. Applicationコンテキストの利用
  2. WeakReferenceによるActivity保持
  3. ライフサイクル監視によるリソース解放

DCLでのvolatileの役割

  • 命令順序の最適化問題:
    instance = new Singleton(); が順序変更される可能性
  • happens-before規則:volatileが可視性と順序を保証
  • JDK5+以降でDCLが動作可能

Kotlinのobjectと手動実装の違い

特徴 object宣言 手動シングルトン
スレッドセーフ 自動保証 手動実装が必要
遅延初期化 初アクセス時 自由に制御可能
シリアライズ対応 追加処理必要 readResolveでカスタマイズ

タグ: Android singleton Kotlin Context MemoryLeak

6月17日 18:50 投稿