AndroidのKotlinでシングルトンを使う方法

 どうも、ケーサンヨーシです。

今回は、Android開発でのKotlinにおけるシングルトンの話です。

それでは、いってみましょう。

■□■□■

シングルトンとは、インスタンスが一つになるような設計モデルのことです。つまり、シングルインスタンスのことをシングルトンというわけです。

シングルトンのクラスを実現する場合、一人でプログラムをしている分には、インスタンスが一つになるように注意してプログラムを書けばいいんですが、複数人でプログラムを書く場合、なにかの事故でインスタンスが複数になってしまう可能性があります。

そこで、インスタンスが一つになるように強制することができる書き方が、Kotlinには存在します。具体的には、以下の2つがあります。

①Objectクラスを使用する
②自身のCompanion Object内にインスタンスを保持する


①Objectクラスを使用する

この方法は、Kotlinという言語のデフォルトの機能として、シングルトンに対応することができるようになっていることを利用します。

使用する方法は至って簡単、Classの代わりにObjectを付けるだけです。

Object TestSingleton{
    var a = 100;
    
    fun add(int _a,int _b ):Int{
        return _a + _b
    }
}

変数やクラスにアクセスするには、以下のように記述します。

// 変数aにアクセス
TestSingleton.a

// ファンクションaddにアクセス
TestSingleton.add(10,20)

インスタンスを作成する必要はありません。どこから呼び出す場合であっても、クラス名.変数名orファンクション名でアクセスできます。

非常に簡単に扱えますが、この方法ではクラス引数が使用できないという欠点があります。


②自身のCompanion Object内にインスタンスを保持する

この方法は、KotlinのCompanion Objectを応用した方法であり、少しトリッキーです。

使用する方法は、対象のクラスの中にCompanion objectを用意し、その中の変数に、自身のインスタンスを保持します。

Companion object内には、自身のインスタンス用の変数以外に、インスタンスを取得するファンクションがあり、このファンクションを経由することで、インスタンスが一つしか無い状態にします。

Class TestSingleton{
    Companion object{
        // インスタンス保持用変数
        var instance:TestSingleton? = null

        // インスタンス取得用ファンクション
        fun getInstance:TestSingleton{
            if(instance == null){
                instance = this
            }
            return instance
        }
    }

    // 以下クラスの中身
    var a = 100;
    
    fun add(int _a,int _b ):Int{
        return _a + _b
    }
}

変数やファンクションにアクセスする場合は、以下のようにします。

// 変数aにアクセス
val ts = TestSingleton.getInstance()
ts.a

// ファンクションaddにアクセス
val ts = TestSingleton.getInstance()
ts.add(10,20)

まずは、クラス内のgetInstanceファンクションにアクセスして、クラスのインスタンスを取得します。このファンクションは、初めてアクセスする時には自身のクラスのインスタンスを作成し、2回目以降にアクセスした時には、最初に作成したインスタンスを返すようになっています。後は、取得したインスタンスを利用してフィールドにアクセスするだけです。

Companion objectブロック内に書かれた変数やファンクションは、どのインスタンスからアクセスしても結果を保持できる、所謂静的フィールドになるため、今回の書き方は、それを応用した方法というわけです。

この方法であれば、クラス引数も使用できるため、Companion Objectの部分を追加する以外は通常のクラスのように使用することができます。


一般的な注意点

さて、Kotlinでシングルトンを「強制的に」実現する方法を紹介しましたが、この2つの方法はどちらも共通する欠点があるため、それを抑えておく必要があります。

①と②の方法は、どちらも静的フィールドを使用してインスタンスが一つであることを実現しています。もし、これらのシングルトンクラスの中に、他のクラスのインスタンスを変数として保持した場合、メモリーリークが発生する可能性があります。

通常のクラスは、インスタンスがどこにも保持されていなければ勝手に消滅するようにできていますが、静的フィールドはいつどこからアクセスされるか分からないため、基本的に勝手に消滅することがありません。

また、静的フィールドの中にインスタンスが保持されている場合、その保持されているクラスも消滅しなくなります。

そのため、勝手にインスタンスを消滅させることができなくなり、それらが積み重なってメモリーリークするというわけです。


Androidでの注意点

私もAndroidでシングルトンを使用した時にメモリーリークを起こしたわけですが、この時、今まで知らない挙動をしたので、これも記述しておきます。

Androidでは、アプリを実行した際に呼ばれるファンクションが、ライフサイクルという仕組みで決まっています。

アプリが起動した時にはonResumeファンクションが呼ばれる、アプリが閉じた時にはonPauseファンクションが呼ばれるなどです。

アプリが完全に落ちた場合には、これらのファンクションやクラス内に保持されている変数は全て真っ白にクリアされ、次にアプリを起動する際には、画面の描写などを最初からやり直すという流れになっています。

しかしここで注意なのが、静的フィールドはアプリを落としてもクリアされないことがあるという点です。

そのため、たとえば画面描写用にActivityクラスのインスタンスを、シングルトンなどの静的フィールドに保持ていた場合、アプリが再起動すると、Activityクラスのインスタンスが新しくなっているのに、古いインスタンスを保持したままになってしまい、このインスタンスを使用した処理がなに一つ動かなくなってしまいます。

この時、アプリが落ちればまだいいんですが、アプリは落ちないので、画面は出ているけどボタンが効かないみたいな状態になるわけです。

Android Studioで開発している場合、ある程度は注意してくれるんですが、100%注意してくれるわけではないので、静的フィールドを使用する時には、インスタンスを入れることが無いように注意しましょう。

■□■□■

というわけで、AndroidのKotlinでシングルトンを使用する方法と注意点を紹介しました。

今回のことで、正直静的フィールドを使うことに抵抗がでてきました。どんなに注意してしても、やる時にはやってしまうし、アプリ側もAndroid Studio側もそのことを注意してはくれないからです。

Kotlinという言語は、そもそも静的フィールドを使わないことを推奨している言語なのですが、その理由が分かったような気がしました。

これからシングルトンが必要な時は、強制させることなく、自身で注意してインスタンスが一つになるようにする方針で行こうかと思います。

これはこれでどうなんだっていう説はありますけどね、、、。

以上!

このブログの人気の投稿

動画編集ソフト「Shotcut」で縦向きの動画を横向きにする方法。

Audacityで音声部分のみを自動カットする方法

Android Studioでの動的なLayoutの幅と高さの変更方法