web-dev-qa-db-ja.com

Androidアプリケーション内のすべてのアクティビティによって共有されるSQLiteOpenHelperの1つのインスタンスを持つことは問題ありませんか?

SQLiteOpenHelperの単一のインスタンスをサブクラス化されたApplicationのメンバーとして保持し、SQLiteDatabaseのインスタンスを必要とするすべてのアクティビティに1つのヘルパーから取得してもよいでしょうか?

39
Julian A.

単一のSQLiteOpenHelperインスタンスを使用すると、スレッドの場合に役立ちます。すべてのスレッドが共通のSQLiteDatabaseを共有するため、操作の同期が提供されます。

ただし、Applicationのサブクラスは作成しません。あなたのSQLiteOpenHelperである静的データメンバーを持っているだけです。どちらのアプローチでも、どこからでもアクセスできるものが提供されます。ただし、Applicationの-​​oneサブクラスしか存在しないため、_otherApplicationのサブクラス(たとえば、GreenDroid IIRCが1つ必要です)。静的データメンバーを使用すると、それを回避できます。ただし、この静的Application(コンストラクターパラメーター)をインスタンス化するときはContextSQLiteOpenHelperを使用して、他のContextをリークしないようにしてください。

また、複数のスレッドを処理していない場合は、コンポーネントごとに1つのSQLiteOpenHelperインスタンスを使用するだけで、メモリリークの問題を回避できます。ただし、実際にはshouldは複数のスレッド(例:Loader)を処理しているため、この推奨事項は、一部の書籍にあるような些細なアプリケーションにのみ関連しています。 。:-)

41
CommonsWare

この件に関する私のブログ投稿を表示するには、ここをクリックしてください。


CommonsWareは(いつものように)正しいです。彼の投稿を拡張して、考えられる3つのアプローチを示すサンプルコードを次に示します。これらにより、アプリケーション全体でデータベースにアクセスできます。

アプローチ#1: `Application`をサブクラス化する

アプリケーションがそれほど複雑にならないことがわかっている場合(つまり、Applicationのサブクラスが1つだけになることがわかっている場合)、Applicationのサブクラスを作成して、メインアクティビティはそれを拡張します。これにより、データベースの1つのインスタンスがアプリケーションのライフサイクル全体で実行されます。

_public class MainApplication extends Application {

    /**
     * see NotePad tutorial for an example implementation of DataDbAdapter
     */
    private static DataDbAdapter mDbHelper;

    /**
     * Called when the application is starting, before any other 
     * application objects have been created. Implementations 
     * should be as quick as possible...
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mDbHelper = new DataDbAdapter(this);
        mDbHelper.open();
    }

    public static DataDbAdapter getDatabaseHelper() {
        return mDbHelper;
    }
}
_

アプローチ#2: `SQLiteOpenHelper`を静的データメンバーにする

これは完全な実装ではありませんが、DatabaseHelperクラスを正しく設計する方法についての良いアイデアを与えるはずです。静的ファクトリメソッドは、常にDatabaseHelperインスタンスが1つだけ存在することを保証します。

_/**
 * create custom DatabaseHelper class that extends SQLiteOpenHelper
 */
public class DatabaseHelper extends SQLiteOpenHelper { 
    private static DatabaseHelper mInstance = null;

    private static final String DATABASE_NAME = "databaseName";
    private static final String DATABASE_TABLE = "tableName";
    private static final int DATABASE_VERSION = 1;

    private Context mCxt;

    public static DatabaseHelper getInstance(Context ctx) {
        /** 
         * use the application context as suggested by CommonsWare.
         * this will ensure that you dont accidentally leak an Activitys
         * context (see this article for more information: 
         * http://developer.Android.com/resources/articles/avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    /**
     * constructor should be private to prevent direct instantiation.
     * make call to static factory method "getInstance()" instead.
     */
    private DatabaseHelper(Context ctx) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.mCtx = ctx;
    }
}
_

アプローチ#3:SQLiteデータベースを `ContentProvider`で抽象化する

これが私が提案するアプローチです。まず、新しいLoaderManagerクラスはContentProvidersに大きく依存しているため、アクティビティまたはフラグメントに_LoaderManager.LoaderCallbacks<Cursor>_を実装したい場合は、これを利用することをお勧めします。アプリケーションにContentProviderを実装する必要があります。さらに、ContentProvidersでシングルトンデータベースヘルパーを作成することについて心配する必要はありません。アクティビティからgetContentResolver()を呼び出すだけで、システムがすべてを処理します(つまり、複数のインスタンスが作成されないようにシングルトンパターンを設計する必要はありません)。

お役に立てば幸いです。

46
Alex Lockwood

複数のスレッドが同じsqliteデータベースを開いたり閉じたりする可能性があるAndroidアプリケーション用の拡張SQLiteOpenHelperであるMultiThreadSQLiteOpenHelperを作成しました。

Closeメソッドを呼び出す代わりに、スレッドはデータベースを閉じるを要求し、スレッドが閉じたデータベースでクエリを実行できないようにします。

各スレッドがクローズを要求した場合、クローズが実際に実行されます。各アクティビティまたはスレッド(ui-threadおよびuser-threads)は、再開時にデータベースに対してオープンコールを実行し、一時停止または終了時にデータベースのクローズを要求します。

ここで入手可能なソースコードとサンプル: https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

7
d4rxh4wx

私はこのトピックについて多くの調査を行い、コモンウェアが言及したすべてのポイントに同意します。しかし、私は誰もがここで見逃している重要な点があると思います。この質問への答えは完全にユースケースに依存するため、アプリケーションが複数のスレッドを使用してデータベースを読み取り、シングルトンは、データベースへの単一の接続があるため、すべての機能が同期され、シリアルに実行されるため、パフォーマンスに大きな影響を与えます。ところで、オープンソースは素晴らしいです。コードを詳しく調べて、何が起こっているのかを確認できます。そのことといくつかのテストから、次のことが正しいことがわかりました。

Sqlite takes care of the file level locking.  Many threads can read, one can write.  The locks prevent more than one writing.
Android implements some Java locking in SQLiteDatabase to help keep things straight.
If you go crazy and hammer the database from many threads, your database will (or should) not be corrupted.

実際の個別の接続から同時にデータベースに書き込もうとすると、失敗します。最初が完了するまで待機せず、次に書き込みます。それは単にあなたの変更を書きません。さらに悪いことに、SQLiteDatabaseで適切なバージョンの挿入/更新を呼び出さない場合、例外は発生しません。 LogCatにメッセージが表示されるだけです。

最初の問題は、実際の異なる接続です。オープンソースコードの優れた点は、すぐに調査して何が起こっているかを確認できることです。 SQLiteOpenHelperクラスはいくつかの面白いことをします。読み取り専用のデータベース接続と読み取り/書き込み接続を取得する方法はありますが、内部では常に同じ接続です。ファイル書き込みエラーがないと仮定すると、読み取り専用接続でさえ、実際には単一の読み取り/書き込み接続です。かなり面白いです。そのため、アプリで1つのヘルパーインスタンスを使用すると、たとえ複数のスレッドからであっても、決してreally複数の接続を使用することはありません。

また、各ヘルパーのインスタンスが1つだけのSQLiteDatabaseクラスは、Java=レベルのロック自体を実装します。したがって、実際にデータベース操作を実行しているとき、他のすべてのdb操作はロックアウトされます。したがって、複数のスレッドが処理を行っている場合でも、データベースのパフォーマンスを最大化するためにそれを実行している場合は、悪いニュースがいくつかあります。

興味深い観察

1つの書き込みスレッドをオフにすると、1つのスレッドのみがデータベースに書き込み、別の読み取りがあり、両方に独自の接続がある場合、読み取りパフォーマンスはWAYアップし、ロックが表示されませんそれは追求すべきものです。書き込みバッチ処理ではまだ試していません。

何らかの種類の更新を複数実行する場合は、トランザクションでラップします。トランザクションで行う50回の更新には、トランザクション外の1回の更新と同じ時間がかかるようです。私の推測では、トランザクション呼び出し以外では、各更新はデータベースへの変更をディスクに書き込もうとします。トランザクション内では、書き込みは1つのブロックで行われ、書き込みのオーバーヘッドによって更新ロジック自体が小さくなります。

4
user1530779

はい、それはあなたがそれについて取り組むべき方法です、データベースのインスタンスを必要とする活動のためのヘルパークラスがあります。

1