2013年6月21日金曜日

[Android]try-with-resources風に処理したい

IO系の処理などで、Java1.7の言語仕様で追加されたtry-with-resources構文をつかうとすっきりかけるんですが、現時点で、AndroidのJavaランタイムDalvikの処理系は1.6系です。そのためJava1.7以降に追加された言語仕様を使うことができません。

そこで、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 件のコメント: