Android:デバッグのために動作ログをファイルに出力する

アプリ開発を行なっていると、屋外でのデバッグ時などでUSBケーブルでのADB接続が出来ない場合や、特定の動作ログをファイルに出力したい時があります。

ですが、ただ実装したのでは芸が無いので、少しオブジェクト指向を意識して実装してみました。

オブジェクト指向を意識したログ出力クラス構成

オブジェクト指向プログラミングの基本は、最初に汎用的なクラスを作って、それを目的(開発案件)に応じて特化させていくことにあると思います。

まずは汎用的なクラス

データを渡すとそれに改行コード(CRLF)を付けて書き込んでいく単純なクラスです。汎用的かどうかはわかりませんが、ある程度他に使い道もありそうなクラスではないでしょうか。

クラス名、メソッド名などもログ出力という目的を意識せず、なるべく無難なネーミングにしています。

以下の例をそのまま使用すると、ファイルは Android/data/[package name]/files 配下に出力されます。

※ダブルクリックで全選択出来ます。

/**
 * データに改行をつけてファイルに出力するクラス
 *
 * @author
 *
 */
public class OneLineWriter {
	/** ファイル最大数 */
	private static final int FILE_MAX = 100;
	/** 改行コード */
	private static final byte[] CRLF = "\r\n".getBytes();
	/** ファイル出力ストリーム */
	private FileOutputStream mOut;

	/**
	 * コンストラクタ
	 *
	 * @param context
	 *            コンテキスト
	 * @param fileName
	 *            ファイル名
	 * @param ext
	 *            拡張子名(".txt"のように指定すること)
	 * @throws FileNotFoundException
	 */
	public OneLineWriter(Context context, String fileName, String ext) throws FileNotFoundException {
		// 同名のファイルが存在する場合、ファイル名の末尾にカウントを付与する
		for (int i = 0; i < FILE_MAX; i++) {
			String tmpName = new String(fileName);
			if (i > 0) {
				// 1番目のファイルにはカウントの付与を行わない
				tmpName += String.format("_%02d", i);
			}
			tmpName += ext;

			// Android/data/[package name]/files 配下に保存されます
			File file = new File(context.getExternalFilesDir(null), tmpName);
			if (!file.exists()) {
				mOut = new FileOutputStream(file);
				return;
			}
		}
		throw new FileNotFoundException("reached file max, should give a different filename.");
	}

	/**
	 * ファイル出力
	 *
	 * @param data
	 *            出力データ
	 * @throws IOException
	 */
	public void output(String data) throws IOException {
		output(data.getBytes());
	}

	/**
	 * ファイル出力
	 *
	 * @param data
	 *            出力データ
	 * @throws IOException
	 */
	public void output(byte[] data) throws IOException {
		if (mOut != null) {
			byte[] tmp = new byte[data.length + CRLF.length];
			System.arraycopy(data, 0, tmp, 0, data.length);
			System.arraycopy(CRLF, 0, tmp, data.length, CRLF.length);
			mOut.write(tmp);
		}
	}

	/**
	 * ファイル出力ストリームを閉じる
	 *
	 * @throws IOException
	 */
	public void close() throws IOException {
		if (mOut != null) {
			mOut.close();
		}
	}
}

次に、汎用的なクラスをラップしたクラス

上述のOneLineWriterクラスを使用して、ログ出力を行うクラスです。

ここでは仮想クラスとして宣言し、書き込みメソッドとファイルクローズメソッドのみを実装して、OneLineWriterクラスの生成(=ログファイル名の決定)はサブクラスに任せることにします。

※ダブルクリックで全選択出来ます。

/**
 * ログ出力仮想クラス
 *
 * @author
 *
 */
public abstract class AbstractLogger {
	/** ファイル出力クラス */
	private OneLineWriter mWriter;

	/**
	 * コンストラクタ
	 *
	 * @param context
	 *            コンテキスト
	 */
	public AbstractLogger(Context context) {
		try {
			mWriter = makeWriter(context);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * ファイル出力クラスのインスタンスを生成する
	 *
	 * @return ファイル出力クラスインスタンス
	 * @throws FileNotFoundException
	 */
	protected abstract OneLineWriter makeWriter(Context context) throws FileNotFoundException;

	/**
	 * ファイル出力
	 *
	 * @param log
	 *            出力データ
	 */
	public void write(byte[] log) {
		if (mWriter != null) {
			return;
		}
		try {
			mWriter.output(log);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * ファイルクローズ
	 */
	public void close() {
		if (mWriter != null) {
			return;
		}
		try {
			mWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

最後に、実際に使用するためのクラス

このサンプルでは、SimpleDateLoggerクラスのインスタンスを生成した時点のシステム時刻が、そのままログファイル名になります。

具体的には、makeWriter()メソッドにてシステム時刻の取得、そしてファイル名とファイル拡張子を指定して、OneLineWriterクラスを生成しています。

このメソッドはAbstractLoggerクラスでabstractで宣言されているため、サブクラス側で自由に実装することが可能です。

※ダブルクリックで全選択出来ます。

/**
 * インスタンスが生成された時点のシステム時刻をファイル名にしてログ出力をするクラス
 *
 * @author
 *
 */
public class SimpleDateLogger extends AbstractLogger {
	/** 拡張子 */
	private static final String EXT = ".log";
	/** 日付フォーマット */
	private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

	/**
	 * コンストラクタ
	 *
	 * @param context
	 *            コンテキスト
	 */
	public SimpleDateLogger(Context context) {
		super(context);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * jp.co.opt.drivesupport.util.AbstractLogger#makeWriter(android.content
	 * .Context)
	 */
	@Override
	protected OneLineWriter makeWriter(Context context) throws FileNotFoundException {
		// サブクラスでファイル名と拡張子名を決定出来る
		String fileName = mDateFormat.format(new Date());
		return new OneLineWriter(context, fileName, EXT);
	}
}

どのような時に使えるのか?

作っておいてあれですが、普通ならあまり使い道が無いかもしれません。

例えば、1つのアプリケーションの中で、いくつか違ったファイル名でデータ出力しなければならない時など・・・でしょうか。

でもまぁ、オブジェクト指向設計の練習には、こういったシンプルなクラス構成が一番です。

スポンサーリンク


コメントを残す

このページの先頭へ