スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Androidでstatic変数が勝手にクリアされる問題と対処法

前回、Androidでは端末のメモリが不足するとstatic変数がクリアされたり、
Activityのメンバ変数がクリアされるという問題を紹介しました。

とはいえ、Object型変数であれば常に null になることを想定しなければならないとすると毎回 null チェックをしなければならないことになり、プログラムとしてもかなり可読性が悪いばかりか、不具合発生時に不具合を見逃す可能性も高くなります。

じゃあどうすればいい?ということになりますが、具体的な対処法の前に、まずはstatic変数がクリアされる理由について詳しく紹介します。


01. 端末がメモリ不足になってアプリを再アクティブにした場合、クラスロードから再度実行される

端末がメモリ不足になると、static変数やActivityのメンバ変数がクリアされると紹介しましたが、このとき、Javaとしてどのような動きをするかというと、クラスロードから再度実行されます

そもそもクラスロードとは何かということを簡単に説明しますと、「プログラムを実行する際、対象となるクラスデータをメモリ上に読み込むこと」を指します。そして、通常Javaでは一度クラスロードされたクラスはアプリケーションを再起動しない限りはメモリ上へ残り続けます。

しかし、Androidではメモリが少なくなるとこのクラスロードされたデータすらをもメモリ上から消し去ってしまうため、各クラスに保持された static 変数がクリアされてしまうというわけです。


では、実際に例を出します。
以下のクラスを見てください。


/**
* テストクラス。
*
* @author Kou
*/
public class Test {

/**
* static finalの文字列
*/
public static final String TEST_LABEL = "Test";

/**
* staticの文字列
*/
private static String testString;



/**
* テスト用文字列を設定する。
*
* @param str 設定する文字列
*
public static void setTestString(final String str) {

testString = str;

}

}


上記のクラスには、2つのstatic変数があります。
そして今、setTestStringメソッドを使って、"TEST String" という文字列を設定した状態とします。

それでは、Android端末がメモリ不足になった場合、
各々のメンバ変数はどのような値になるでしょうか?



正解は

・TEST_LABEL → "Test"
・testString → null

となります。


前述のとおり、Androidではメモリ不足になるとクラスロードが再実行されます。Javaの仕様としては、クラスロードが実行されると全てのstatic変数の初期化を行います。このとき、メンバ変数宣言箇所で直接値を代入していたら、その値が初期値となります。そのため、宣言時に代入処理をしている TEST_LABEL は "Test" という値になり、代入処理をしていない testString は null となるわけです。

通常のJavaではクラスロードは基本一度しか実行されませんが、Androidでは何度も実行される可能性があるために、このような現象が起きるというわけです。


02. static変数をAndroidで上手く利用するには、static finalを利用する

さて、Androidで static 変数が突然 null になる理屈はご理解頂けたかと思いますが、そうなると「Androidではstatic変数は使えないのか?」と思っている方もいらっしゃるかもしれませんが、そんなことはありません。

01でも説明したとおり、要はクラスロードされたときに元に戻っていれば問題ないのです。Javaでは static final で宣言した値は定数として扱われます。static変数に対して付けられた final という修飾子は、メンバ変数宣言時の代入もしくはstaticイニシャライザーでの代入を強制させるものですので、クラスロードされたときの初期値を明確に設定することが可能になります。(別に final を付けていなくても同じですが、final化することで代入漏れを防げるのと、最適化が効きやすくなるので final化することが推奨されます)



/**
* テストクラス。
*
* @author Kou
*/
public class Test {

/**
* static finalの文字列
* (static finalの値は必ず何かしらの値の代入を強制されるので、
* クラスロード時に意図した値を設定することが可能になる)
*/
public static final String TEST_LABEL = "Test";

/**
* 文字列検索マップ
*/
private static final Map<String, String> SEARCH_MAP = new HashMap<String, String>();

/**
* staticの文字列
*/
private static String testString;


/**
* staticイニシャライザー
* (ここで定義された処理はクラスロード時に実行されるので、
* キャッシュマップなどの再構築に利用出来る)
*/
static {

// 検索文字列をマップへ設定する
SEARCH_MAP.put("test", "てすと");

}



/**
* テスト用文字列を設定する。
*
* @param str 設定する文字列
*
public static void setTestString(final String str) {

testString = str;

}

}




しかし、現実的な問題として
例えばクラスのインスタンス数を static 変数に保持していたとしたら
その値もクリアされると 0 から開始されますので、メモリ不足になる前にどの値になっていたのかわからないために復元することが難しくなります。

そのため、前回も少し触れましたが、Androidにおいてはstatic変数をキャッシュ以外の目的で利用してはいけません。もう少しわかりやすくいえば、永続的なデータとして利用してはいけません。Androidではこの問題に対処するために、プリファレンスという仕組みが存在します。逆にいえば、そもそも static 変数が常に値を保持し続けられるのであれば、プリファレンスなどという仕組みは別に必要ないわけです。

今まで何気なくプリファレンスを利用していた方もいたかもしれませんが、このような背景を知っているのといないのとでは、プリファレンスとstatic変数に対する扱いも大きく変わってくるかと思います。


以上の情報をまとめると、


  1. static変数は永続的データとして(キャッシュ以外の目的で)利用してはいけない
  2. 永続データとして、アプリケーション開始〜終了まで一貫して値を保持し続けたい場合は、staticではなくプリファレンス(PreferencesやSharedPreferencesなど)を使う
  3. static final(定数化)したり、staticイニシャライザーを利用することで、クラスロード時にある程度データの再構築が可能になる


これでAndroidにおける static 変数の扱いについてはご理解頂けたかと思います。
次回は Activity におけるメンバ変数の保存・復元について詳しく説明したいと思います。
スポンサーサイト

コメントの投稿

非公開コメント

プロフィール

Kou

Author:Kou
モバイル関連の開発ばかりやってる人のブログです。たまにWebもやります。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。