2012年11月5日月曜日

Android - SDカードにログを吐く

Androidアプリを開発していると、大抵の用途はLogcatというEclipseやコンソールに対してログを出す機構がある為これで事足りるのだが、 特に以下のような場合に、PCですぐにデバッグできない為にログを見るのが難しい。

GPSロガー系アプリのように、サービスで動作させて定期的に滞りなく処理が動作しているかの確認
AlarmManagerを使用した定期的処理(勿論AlarmManager動作直後にLogcatを見れば良いのだが、利便性など)
地下鉄内などにおける通信失敗等のイレギュラー系のテスト
今回、以下のような観点でSDカードにログを取る為のクラスを作成してみた。

ログファイルは1日分を1ファイルとする
実行時間を知る為、ログの先頭に時分秒ミリ秒まで時刻を記載する
アプリをアンインストールした際にログファイルも削除する。これを実現する為にAPI Level8から導入されたContext#getExternalFilesDir()を使用している
SDカードがマウントされていない場合はログ出力しないし、動的にマウントされた場合(裏でUSBメモリーのように使われた場合など)にも対応する(書きこむ時にマウント状態をチェックする)
パフォーマンスの観点から、実際に書きこむ際になるべくオブジェクトをnewしない
本番用の設定(何か定数を切って切り替えている事を想定)でのビルドの際はログ出力は行わない
結果、以下のようなMyLoggerクラスとなった。

/**
* SDカード内のアプリで決められた領域にログを記録する為のロガー.
* Context#getExternalFilesDir()を使用している為, 使用はAndroid 2.2以上に限られる.
*
* @author Hideyuki Kojima
*/
public final class MyLogger {

/** 本番ビルドかどうか. */
// TODO 本来はアプリ内で何らかの形で1箇所に定数で切るべき.
private static final boolean IS_PRODUCT = false;

/** ログファイル名のフォーマット. */
private static final SimpleDateFormat FILE_NAME_FORMAT = new SimpleDateFormat("yyyyMMdd");

/** ログの先頭に記載する時刻フォーマット. */
private static final SimpleDateFormat LOG_PREFIX_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS ");

/** Singletonに管理されているロガーのインスタンス. */
private static MyLogger logger;

/** ディレクトリパス. */
private final String directoryPath;

/** ログファイルを置くディレクトリ. */
private File directory = null;

/**
* コンストラクタ. ログファイル保存用のディレクトリパスを初期化する.
* この時点でSDカードがマウントされていれば, 対象ディレクトリのファイルオブジェクトも初期化する.
*
* @param context
*/
private MyLogger(Context context) {
directoryPath = context.getExternalFilesDir(null).getParent() + File.separator + "logs";
checkMediaMounted();
}

/**
* SDカード内のパッケージ名のディレクトリ内にログを取るロガーを返すstaticファクトリーメソッド.
*
* @param context コンテキスト
* @return パッケージ名のディレクトリ内にログを取る為のロガー
*/
public static MyLogger getInstance(Context context) {
if (logger == null) {
logger = new MyLogger(context);
}
return logger;
}

/**
* ログファイルにログを書き込む.
*
* @param log ログ
*/
public void write(final String log) {
// 本番ビルドの場合は吐かない. ログディレクトリを指すオブジェクトが初期化されていない場合,
// SDカードがマウントされているかのチェックを行う. マウントされていない場合は何もしない.
if (IS_PRODUCT || directory == null && !checkMediaMounted()) {
return;
}

// ログファイルを生成. 名前はyyyyMMdd.txtとする.
Date date = new Date();
File file = new File(directoryPath + File.separator + FILE_NAME_FORMAT.format(date) + ".txt");

// ログファイルに書き込み.
try {
FileWriter writer = new FileWriter(file, true);
writer.write(LOG_PREFIX_FORMAT.format(date));
writer.write(log);
writer.write(System.getProperty("line.separator"));
writer.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* SDカードがマウントされているかをチェックする.
* マウントされていたら, ログファイルを置くディレクトリを初期化する.
*
* @return SDカードがマウントされているか
*/
private boolean checkMediaMounted() {
// SDカードがマウントされているかのチェックを行う.
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return false;
}

// ログファイルのディレクトリ存在チェックと, 存在しなかったら再帰的にディレクトリ生成.
directory = new File(directoryPath);
if (!directory.exists()) {
directory.mkdirs();
}
return true;
}
}
実際の利用時は以下のように書く。(以下Activity内のコードとする)

// thisはこの場合Activity(ログ出力したい「実行者」)を指す
MyLogger logger = MyLogger.getInstance(this);
logger.write("ログ文字列");
これで、SDカード内の/Android/data/(パッケージ名)(環境によって微妙に異なる可能性がある)にログファイルが入るはずなので確認する。

0 件のコメント:

コメントを投稿