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 件のコメント:
コメントを投稿