FLYING

/* TODO: 気の利いた説明を書く */

AndroidアプリでContextを持ち回したい話

2012-06-16T04:04:00+09:00:コンストラクタでstaticフィールドを初期化するよりも,onCreateで初期化した方がいいかもしれない。

今まではSingletonなAppクラスを作って,MainActivityのonCreateでApplicationContextをAppのフィールドに代入,Appのインスタンスを通じて各クラスから参照する……という方法でApplicationContextを持ち回していたのだけど,これだと場合によってはAppが持っているフィールドが破棄されて,NullPointerExceptionを引き起こす可能性があることがわかった(アプリのライフサイクルとプロセスのライフサイクルが一致しないため?)。

つい最近になってandroid.app.Applicationというクラスの存在を知ったので,次のようにしてApplicationのインスタンスをstatic参照できるようにしてみた。個人的な理解では「アプリのプロセスが存在する=android.app.Applicationのインスタンスが存在する」なので,アプリ内の各クラスからApplicationないしはApplicationContextのインスタンスを取得することができるハズだ。

public class App extends android.app.Application {

	public static final String NAME = "YourAppName";
	public static final String TAG = "YourAppName";
	public static final int MP = ViewGroup.LayoutParams.FILL_PARENT;
	public static final int WC = ViewGroup.LayoutParams.WRAP_CONTENT;

	// コンテキスト
	private static App instance;
	public App() {
		instance = this;
	}
	public static App getInstance() {
		return instance;
	}

	// ログ出力用
	public static void Logd(Object obj, String str) {
		android.util.Log.d(App.TAG,
				String.format("@%s - %s", obj.toString(), str));
	}
	public static void Loge(Object obj, Exception e) {
		e.printStackTrace();
		android.util.Log.e(App.TAG,
				String.format("@%s - %s", obj.toString(), e.toString()));
	}

	// ファイルシステム
	public static File getStorageDir() {
		File file = Environment.getExternalStorageDirectory();
		return new File(file, App.NAME);
	}
	public static boolean isStorageAvailable() {
		return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
	}
	public static String makeUUIDString() {
		return UUID.randomUUID().toString();
	}

	// アプリ設定の取得
	public SharedPreferences getSharedPreferences() {
		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
	}

	// DPとPXの相互変換
	public int dp2px(int dp) {
		return (int) (dp * getResources().getDisplayMetrics().density);
	}
	public int px2dp(int px) {
		return (int) (px / getResources().getDisplayMetrics().density);
	}
}

xmlからapplication要素のandroid:name属性でApplicationクラスを指定するのも忘れずに。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.app.name"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="10" />

    <application
        android:name=".App"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".activity.HomeActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

たとえば,他のクラスからはAppを通じて次のようにResourceを参照できる。

Drawable d = App.getInstance().getResources().getDrawable(android.R.drawable.icon);

前のエントリでも書いたが,Android開発では何をするにも(Application)Contextが必要になるので,こういうクラスがあるとないではロジックの見通しが全然変わると思う。もちろん,永続化したいデータはActivityがonPauseするタイミングで確実に保存する必要があるので,なんでもかんでもApplicationのフィールドに持たせておけばいい……というわけではないので注意。

Androidのプロセス・アプリ管理のメカニズムについてはまだ理解が浅いので,もし間違いなどありましたらコメ欄etcでご指摘いただけると幸いです。