シングルトン対Androidのアプリケーションコンテキスト?


362

シングルトンの使用に関するいくつかの問題を列挙 し、シングルトンパターンを使用するAndroidアプリケーションのいくつかの例を見たことがあるこの投稿を思い出して、グローバルアプリケーション状態(android.os.Applicationのサブクラス化と取得)を通じて共有される単一のインスタンスの代わりにシングルトンを使用するのは良い考えでしょうcontext.getApplication()を通じて)。

両方のメカニズムにはどのような利点/欠点がありますか?

正直なところ、この投稿のWebアプリケーションでのシングルトンパターンで同じ答えが得られることを期待しています。Androidに適用されます。私は正しいですか?それ以外のDalvikVMの違いは何ですか?

編集:私はいくつかの側面に関与することについて意見を述べたいです:

  • 同期
  • 再利用性
  • テスト中

回答:


295

私はダイアン・ハックボーンの反応に非常に同意しません。プロジェクトからすべてのシングルトンを少しずつ削除して、実際に必要なときに簡単に再作成できる軽量のタスクスコープオブジェクトを優先しています。

シングルトンはテストの悪夢であり、遅延して初期化すると、微妙な副作用(あるスコープから別のスコープに呼び出しを移動すると突然表面化する可能性があります)を伴う「状態不確定性」が発生しますgetInstance()。可視性は別の問題として言及されており、シングルトンは共有状態への「グローバル」(=ランダム)アクセスを意味するため、並行アプリケーションで適切に同期されない場合、微妙なバグが発生する可能性があります。

私はそれをアンチパターンと考えています。それは本質的にグローバルな状態を維持することになる、悪いオブジェクト指向のスタイルです。

あなたの質問に戻るには:

アプリコンテキスト自体はシングルトンと見なすこともできますが、フレームワークで管理され、明確に定義されたライフサイクル、スコープ、アクセスパスがあります。したがって、アプリのグローバル状態を管理する必要がある場合は、他の場所ではなく、ここに移動する必要があると思います。それ以外の場合は、本当にシングルトンオブジェクト必要かどうか、またはシングルトンクラスを書き直して、代わりに手元のタスクを実行する、有効期間が短いオブジェクトをインスタンス化できるかどうかを考え直してください。


131
アプリケーションを推奨している場合は、シングルトンの使用を推奨しています。正直なところ、それを回避する方法はありません。アプリケーションシングルトンであり、セマンティクスは粗雑です。決して使用してはならないシングルトンについての宗教的な議論には触れません。私は実用的であることが好きです-プロセスごとの状態を維持するための良い選択であり、そうすることで物事を簡素化できる場所があり、間違った状況でそれらを使用して、自分の足で撃つこともできます。
ハックボット、2011

18
確かに、「アプリコンテキストはシングルトン自体と見なすことができる」と述べました。違いは、アプリインスタンスでは、ライフサイクルがフレームワークによって処理されるため、足元で自分を撃つことがはるかに難しいことです。Guice、Hivemind、SpringなどのDIフレームワークもシングルトンを使用しますが、これは開発者が気にする必要のない実装の詳細です。自分のコードよりもフレームワークのセマンティクスが正しく実装されていることに依存する方が、一般的に安全だと思います。はい、認めます!:-)
Matthias

93
正直なところ、シングルトンの場合と同じように、足で自分を撃つことを妨げるものではありません。少し混乱しますが、アプリケーションライフサイクルはありません。これは、アプリの起動時(コンポーネントがインスタンス化される前)に作成され、その時点でonCreate()が呼び出されます。これですべてです。それはそこに座って、プロセスが殺されるまで永遠に生きます。シングルトンのように。:)
ハックボット

30
これを混乱させるかもしれない1つのこと-Androidは、プロセス内でアプリを実行し、それらのプロセスのライフサイクルを管理することを中心に設計されています。そのため、Androidでは、シングルトンはそのプロセス管理を活用する非常に自然な方法です。プラットフォームがプロセスのメモリを別の何かのために再利用する必要があるまで、プロセスに何かをキャッシュしたい場合は、その状態をシングルトンに置くことで実現します。
ハックボット

7
わかりました。自己管理型のシングルトンから一歩踏み出して以来、振り返ったことはありません。私たちは今、軽量のDIスタイルのソリューションを選択しています。そこでは、1つのファクトリシングルトン(RootFactory)を保持します。これは、アプリインスタンスによって管理されます(可能であればデリゲートです)。このシングルトンは、すべてのアプリコンポーネントが依存する共通の依存関係を管理しますが、インスタンス化は1つの場所(アプリクラス)で管理されます。このアプローチでは1つのシングルトンが残りますが、それはApplicationクラスに限定されるため、他のコードモジュールはその「詳細」を知りません。
Matthias

231

シングルトンを強くお勧めします。コンテキストが必要なシングルトンがある場合は、次のようにします。

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

私はアプリケーションよりもシングルトンの方を好みます。これは、アプリをより組織化してモジュール化するのに役立つためです。アプリ全体のすべてのグローバルな状態を維持する必要がある1つの場所ではなく、個々の部分がそれ自体で処理できます。また、シングルトンがすべての初期化をApplication.onCreate()で事前に実行するパスを導くのではなく、(要求に応じて)遅延して初期化するという事実は良いことです。

シングルトンの使用には本質的に問題はありません。理にかなっている場合は、正しく使用してください。ロードされたリソースなどのプロセスごとのキャッシュを維持するために、Androidフレームワークには実際にそれらの多くがあります。

また、単純なアプリケーションの場合、シングルスレッドではマルチスレッディングは問題になりません。これは、アプリへのすべての標準コールバックがプロセスのメインスレッドでディスパッチされるため、スレッドを通じて明示的に導入しない限り、マルチスレッドが発生しないためですコンテンツプロバイダーまたはサービスIBinderを他のプロセスに公開することにより、暗黙的に。

何をしているのかをよく考えてください。:)


1
しばらくして、外部イベントをリッスンしたり、IBinderで共有したりしたい場合は(単純なアプリではないようです)、二重ロック、同期、揮発性を追加する必要がありますよね?答えてくれてありがとう:)
mschonaker 09/10/30

2
外部イベントではありません-BroadcastReceiver.onReceive()もメインスレッドで呼び出されます。
10

2
はい。メインスレッドのディスパッチメカニズムを確認できる資料(コードの方がいいです)を教えていただけますか?私はいくつかの概念を一度に明らかにしてくれると思います。前もって感謝します。
mschonaker

2
これがアプリ側のメインディスパッチコードです:android.git.kernel.org/
p

8
シングルトンの使用には本質的に問題はありません。理にかなっている場合は、正しく使用してください。..確かに、正確に、よく言った。ロードされたリソースのプロセスごとのキャッシュなどを維持するために、Androidフレームワークには実際にそれらがたくさんあります。あなたが言うとおりに。iOSの世界の友達から、iOSの「すべてがシングルトン」です。物理デバイスでは、シングルトンのコンセプトよりも自然なものはありません。gps、時計、ジャイロなど-概念的には、他にどのように設計するかシングルトンとして?そうそう。
Fattie、

22

From:Developer> reference-Application

通常、アプリケーションをサブクラス化する必要はありません。ほとんどの状況で、静的シングルトンは、よりモジュール的な方法で同じ機能を提供できます。シングルトンにグローバルコンテキストが必要な場合(たとえば、ブロードキャストレシーバーを登録するため)、それを取得する関数に、最初にシングルトンを構築するときにContext.getApplicationContext()を内部的に使用するContextを指定できます。


1
また、シングルトンのインターフェイスを記述してgetInstanceを静的にしない場合、シングルトンを使用するクラスのデフォルトコンストラクターで、非デフォルトコンストラクターを介してプロダクションシングルトンを注入することもできます。これは、作成に使用するコンストラクターでもあります。単体テストでシングルトンを使用するクラス。
android.weasel

11

アプリケーションはシングルトンと同じではありません。理由は次のとおりです。

  1. アプリケーションのメソッド(onCreateなど)がuiスレッドで呼び出されます。
  2. シングルトンのメソッドは、どのスレッドでも呼び出すことができます。
  3. Applicationのメソッド「onCreate」では、ハンドラーをインスタンス化できます。
  4. シングルトンがnone-uiスレッドで実行される場合、Handlerをインスタンス化できません。
  5. アプリケーションには、アプリ内のアクティビティのライフサイクルを管理する機能があります。「registerActivityLifecycleCallbacks」メソッドがあります。ただし、シングルトンには機能がありません。

1
注:ハンドラーは任意のスレッドでインスタンス化できます。ドキュメントから:「新しいハンドラーを作成すると、それを作成しているスレッドのスレッド/メッセージキューにバインドされます」
Christ

1
@Christありがとう!ちょうど今、「ルーパーのメカニズム」を学びました。コード「Looper.prepare()」なしでnone-uiスレッドでハンドラーをインスタンス化すると、システムはエラー「java.lang.RuntimeException:Can Looper.prepare()を呼び出していないスレッド内にハンドラを作成しないでください。
sunhang 2014年

11

私は同じ問題を抱えていました:シングルトンまたはサブクラスandroid.os.Applicationを作成しますか?

最初にシングルトンを試しましたが、ある時点で私のアプリがブラウザーを呼び出します

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

問題は、ハンドセットに十分なメモリがない場合、ほとんどのクラス(シングルトンを含む)がクリーンアップされてメモリが確保されるため、ブラウザーからアプリに戻ると毎回クラッシュします。

解決策:必要なデータをApplicationクラスのサブクラス内に配置します。


1
私はしばしばこれが起こるかもしれないと人々が言っ​​ている投稿に出くわしました。したがって、ライフサイクルが文書化され、認識されていることを確認するために、遅延ロードなどを使用して、シングルトンのようなアプリケーションにオブジェクトを単にアタッチします。何百もの画像をアプリケーションオブジェクトに保存しないでください。アプリがバックグラウンドにあり、すべてのアクティビティが破棄されて他のプロセスのためにメモリが解放された場合、メモリから消去されないことを理解しています。
Janusz

アプリケーションの再起動後のシングルトンレイジーロードは、オブジェクトをGCでスイープさせるための正しい方法ではありません。WeakReferencesは正しいですか?
mschonaker

15
本当に?Dalvikはクラスをアンロードし、プログラムの状態を失いますか?そもそも、シングルトンに入れるべきではない、ライフサイクルの限られたActivity関連のオブジェクトをガベージコレクションしているのではないでしょうか。あなたはそのような並外れた主張のためにクララーの例を与えなければなりません!
android.weasel

1
私が知らない変更がない限り、Dalvikはクラスをアンロードしませ。ずっと。彼らが見ている振る舞いは、ブラウザのためのスペースを作るためにバックグラウンドで殺されている彼らのプロセスです。彼らはおそらく「メイン」アクティビティで変数を初期化していましたが、ブラウザから戻ったときに新しいプロセスで作成されなかった可能性があります。
Groxx 2015年

5

両方を同時に検討してください:

  • クラス内の静的インスタンスとしてシングルトンオブジェクトを持ちます。
  • アプリケーションのすべてのsingeltonオブジェクトのシングルトンインスタンスを返す共通のクラス(Context)を持ちます。これには、Contextのメソッド名が意味を持つという利点があります。たとえば、User.getInstance()ではなくcontext.getLoggedinUser()です。

さらに、コンテキストを拡張して、シングルトンオブジェクトへのアクセスだけでなく、context.logOffUser()、context.readSavedData()など、グローバルにアクセスする必要があるいくつかの機能を含めることをお勧めします。おそらく、コンテキストの名前をファサードはそのとき理にかなっています。


4

彼らは実際には同じです。目に見える違いが1つあります。Applicationクラスを使用すると、Application.onCreate()で変数を初期化し、Application.onTerminate()で変数を破棄できます。シングルトンでは、VMの初期化と静態の破壊に依存する必要があります。


16
onTerminateのドキュメントによると、これはエミュレータによってのみ呼び出されます。デバイスでは、そのメソッドはおそらく呼び出されません。developer.android.com/reference/android/app/...
danb

3

私の2セント:

アクティビティが破棄されると、いくつかのシングルトン/スタティックフィールドがリセットされることに気付きました。一部のローエンド2.3デバイスでこれに気付きました。

私の場合は非常に単純でした。私はactivity.onCreate()から呼び出したプライベートファイル「init_done」と静的メソッド「init」を持っているだけです。メソッドinitがアクティビティの再作成時に自身を再実行していたことに気づきました。

私は肯定を証明することはできませんが、シングルトン/クラスが最初に作成/使用されたときに関連している可能性があります。アクティビティが破棄またはリサイクルされると、このアクティビティのみが参照するすべてのクラスもリサイクルされるようです。

シングルトンのインスタンスをApplicationのサブクラスに移動しました。アプリケーションインスタンスからアクセスします。それ以来、問題に再び気づかなかった。

これが誰かの役に立つことを願っています。


3

ことわざの口から...

アプリを開発するとき、アプリ全体でデータ、コンテキスト、またはサービスをグローバルに共有する必要がある場合があります。たとえば、アプリに現在ログインしているユーザーなどのセッションデータがある場合、この情報を公開する必要があります。Androidでは、この問題を解決するためのパターンは、android.app.Applicationインスタンスがすべてのグローバルデータを所有し、Applicationインスタンスを、さまざまなデータとサービスへの静的アクセサーを持つシングルトンとして扱うことです。

Androidアプリを作成する場合、android.app.Applicationクラスのインスタンスは1つだけであることが保証されているため、それをシングルトンとして扱うことは安全です(Google Androidチームによって推奨されます)。つまり、静的なgetInstance()メソッドをアプリケーション実装に安全に追加できます。そのようです:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

私のアクティビティは、finish()を呼び出して(すぐには終了しませんが、最終的には終了します)、Googleストリートビューアーを呼び出します。Eclipseでデバッグすると、ストリートビューアーが呼び出されたときにアプリへの接続が切断されます。これは、(全体の)アプリケーションが閉じられていると理解しており、おそらくメモリを解放するためです(単一のアクティビティが終了してもこの動作は発生しないため) 。それでも、onSaveInstanceState()を使用して状態をバンドルに保存し、スタック内の次のアクティビティのonCreate()メソッドで状態を復元できます。静的シングルトンアプリケーションまたはサブクラス化アプリケーションを使用することにより、アプリケーションを閉じて失う状態に直面します(バンドルに保存しない限り)。だから私の経験から、彼らは国家の保存に関して同じです。Android 4.1.2と4.2.2では接続が失われますが、4.0.7または3.2.4では失われません。


「Android 4.1.2と4.2.2では接続が失われますが、4.0.7または3.2.4では接続が失われていることに気付きました。私の理解では、メモリ回復メカニズムがどこかで変更されたことを示唆しています。」.....デバイスに利用可能なメモリの量や、同じアプリがインストールされていないと思います。したがって、あなたの結論はおそらく正しくないかもしれません
キリスト

@キリスト:はい、あなたは正しいに違いありません。メモリ回復メカニズムがバージョン間で変更された場合、それは奇妙なことです。おそらく、異なるメモリ使用量が異なる動作を引き起こしました。
ピオベザン2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.