そこで、Androidで開放漏れを起こしやすい代表的なリソースを開放するヘルパークラスを実装してみました。コードはこんな感じ。
public class Closer {
private final Stack mTargets = new Stack();
/**
* Closeableの登録.
*
* @param closeable 対象
* @return 入力と同じ
*/
public T put(T closeable) {
if (closeable == null) {
return closeable;
}
mTargets.push(closeable);
return closeable;
}
/**
* SQLiteDatabaseの登録.
*
* @param db 対象
* @return 入力と同じ
*/
public SQLiteDatabase putDb(SQLiteDatabase db) {
if (db == null) {
return db;
}
mTargets.push(new SQLiteDatabaseCloser(db));
return db;
}
/**
* SQLiteDatabaseの登録.
*
* @param helper 対象
* @return 入力と同じ
*/
public SQLiteOpenHelper putDbHelper(SQLiteOpenHelper helper) {
if (helper == null) {
return helper;
}
mTargets.push(new SQLiteOpenHelperCloser(helper));
return helper;
}
/**
* クローズ.
*
* 登録と逆順にクローズする.
*/
public void close() {
while (!mTargets.empty()) {
try {
mTargets.pop().close();
} catch (IOException e) {
Log.w("Closer", "Closer#close error!!", e);
}
}
}
@Override
protected void finalize() throws Throwable {
if (!mTargets.isEmpty()) {
Log.w("Closer", "Closer#close not call!!");
close();
}
super.finalize();
}
/**
* SQLiteDatabaseのラッパー.
*/
private class SQLiteDatabaseCloser implements Closeable {
private final SQLiteDatabase mTarget;
SQLiteDatabaseCloser(SQLiteDatabase target) {
mTarget = target;
}
@Override
public void close() throws IOException {
mTarget.close();
}
}
/**
* SQLiteOpenHelperのラッパー.
*/
private class SQLiteOpenHelperCloser implements Closeable {
private final SQLiteOpenHelper mTarget;
SQLiteOpenHelperCloser(SQLiteOpenHelper target) {
mTarget = target;
}
@Override
public void close() throws IOException {
mTarget.close();
}
}
}
finalizeでクローズ処理を実行しているのは保険です。利用側の実装はこんな感じ。
public void onButton1(View view) {
byte[] b = "あいうえお".getBytes(Charset.defaultCharset());
// ヘルパークラスのインスタンスを用意する
Closer closer = new Closer();
try {
// Closeable系のリソースのインスタンスを生成するとき、Closerを経由する
InputStream in = closer.put(new ByteArrayInputStream(b));
Writer w = closer.put(new StringWriter());
byte[] buf = new byte[128];
in.read(buf);
w.write(new String(buf, Charset.defaultCharset()));
// DBOpenHelperはCloseableではないので専用メソッド経由
SQLiteOpenHelper helper = closer
.putDbHelper(new DBOpenHelper(this));
// SQLiteDatabaseはCloseableではないので専用メソッド経由
SQLiteDatabase db = closer.putDb(helper.getReadableDatabase());
Cursor cursor = closer.put(db.rawQuery("select * from TEST_TABLE",
new String[] {}));
if (cursor.moveToFirst()) {
// 処理
}
} catch (IOException e) {
Log.e("closer", "error!!", e);
} finally {
// 内包するリソースを開放する
closer.close();
}
}
try-with-resources程ではないですが、だいぶすっきりするような気がします。tryブロックの前に変数宣言しなくて済むのがいい感じ(個人的な感覚)。。。ですよね?ちなみにこういったクラスを用意しなかったら、こんな感じ。
public void onButton1(View view) {
byte[] b = "あいうえお".getBytes(Charset.defaultCharset());
InputStream in = null;
Writer w = null;
SQLiteOpenHelper helper = null;
SQLiteDatabase db = null;
Cursor cursor = null;
try {
in = new ByteArrayInputStream(b);
w = new StringWriter();
byte[] buf = new byte[128];
in.read(buf);
w.write(new String(buf, Charset.defaultCharset()));
helper = new DBOpenHelper(this);
db = helper.getReadableDatabase();
cursor = db.rawQuery("select * from TEST_TABLE", new String[] {});
if (cursor.moveToFirst()) {
// 処理
}
} catch (IOException e) {
Log.e("closer", "error!!", e);
} finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
if (helper != null) {
helper.close();
}
if (w != null) {
try {
w.close();
} catch (IOException e) {
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
・・・うんざりですね。
0 件のコメント:
コメントを投稿