41

是否可以将 SQLiteOpenHelper 的单个实例作为子类 Application 的成员,并让所有需要 SQLiteDatabase 实例的活动从一个助手那里获取它?

4

5 回答 5

49

单击此处查看我关于此主题的博客文章。


CommonsWare 是正确的(像往常一样)。扩展他的帖子,这里有一些示例代码,说明了三种可能的方法。这些将允许在整个应用程序中访问数据库。

方法 #1:继承 `Application`

如果您知道您的应用程序不会很复杂(即,如果您知道您最终只会有一个 的子类Application),那么您可以创建一个 的子类Application并让您的主 Activity 扩展它。这确保了数据库的一个实例在应用程序的整个生命周期中运行。

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 实例。

/**
 * 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:使用 `ContentProvider` 抽象 SQLite 数据库

这是我建议的方法。一方面,新LoaderManager类在很大程度上依赖于 ContentProviders,所以如果你想实现一个 Activity 或 Fragment LoaderManager.LoaderCallbacks<Cursor>(我建议你利用它,它很神奇!),你需要ContentProvider为你的应用程序实现一个。此外,您无需担心使用 ContentProviders 创建 Singleton 数据库助手。只需getContentResolver()从 Activity 调用,系统就会为您处理所有事情(换句话说,无需设计单例模式来防止创建多个实例)。

希望有帮助!

于 2012-01-17T02:36:19.400 回答
41

拥有一个SQLiteOpenHelper实例可以帮助处理线程案例。由于所有线程都将共享 common SQLiteDatabase,因此提供了操作的同步。

但是,我不会创建Application. 只要有一个静态数据成员就是你的SQLiteOpenHelper. 这两种方法都可以让您从任何地方访问。但是,只有一个的子类Application,使您更难使用的其他子类Application(例如,GreenDroid 需要一个 IIRC)。使用静态数据成员可以避免这种情况。但是,Application Context在实例化这个静态SQLiteOpenHelper(构造函数参数)时一定要使用,这样就不会泄漏其他Context的 .

而且,在您不处理多个线程的情况下,您可以通过SQLiteOpenHelper每个组件仅使用一个实例来避免任何可能的内存泄漏问题。但是,在实践中,您应该处理多个线程(例如, a Loader),因此此建议仅与琐碎的应用程序相关,例如在某些书籍中找到的应用程序... :-)

于 2012-01-17T01:24:56.210 回答
7

我编写了 MultiThreadSQLiteOpenHelper,它是 Android 应用程序的增强型 SQLiteOpenHelper,其中多个线程可能打开和关闭同一个 sqlite 数据库。

线程不调用 close 方法,而是要求关闭数据库,从而阻止线程对已关闭的数据库执行查询。

如果每个线程都要求关闭,那么实际上会执行关闭。每个活动或线程(ui-thread 和 user-threads)在恢复时对数据库执行打开调用,并在暂停或完成时要求关闭数据库。

此处提供源代码和示例: https ://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

于 2012-03-15T10:15:33.203 回答
4

我对这个话题做了很多研究,我同意 commonware 提到的所有观点。但是我认为每个人都在这里遗漏了一个重要的点,这个问题的答案完全取决于您的用例,因此如果您的应用程序通过多个线程读取数据库 并且仅使用 Singleton 读取会产生巨大的性能影响,因为所有功能都是同步的并且是串行执行的,因为有一个单一的数据库连接顺便说一句,开源很棒。您可以深入研究代码并查看发生了什么。从那和一些测试中,我了解到以下内容是正确的:

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 类做了一些有趣的事情。尽管有一种方法可以获取只读数据库连接和读写连接,但在后台,它始终是相同的连接。假设没有文件写入错误,即使是只读连接也是真正的单一读写连接。挺滑稽的。因此,如果您在应用程序中使用一个帮助程序实例,即使来自多个线程,您也永远不会真正使用多个连接。

此外,每个助手只有一个实例的 SQLiteDatabase 类在其自身上实现了 java 级别的锁定。因此,当您实际执行数据库操作时,所有其他数据库操作都将被锁定。所以,即使你有多个线程在做一些事情,如果你这样做是为了最大限度地提高数据库性能,我有一些坏消息要告诉你。没有好处。

有趣的观察

如果您关闭一个写入线程,那么只有一个线程正在写入数据库,但另一个读取,并且两者都有自己的连接,读取性能会迅速上升,我看不到任何锁定问题。 这是要追求的。我还没有尝试过写批处理。

如果您要执行多个任何类型的更新,请将其包装在事务中。我在事务中执行的 50 次更新似乎与事务外的 1 次更新所用的时间相同。我的猜测是,在事务调用之外,每次更新都会尝试将数据库更改写入磁盘。在事务内部,写入是在一个块中完成的,写入的开销使更新逻辑本身相形见绌。

于 2015-07-03T16:33:33.227 回答
1

是的,你应该这样做,为需要数据库实例的活动提供一个帮助类。

于 2012-01-17T01:19:37.317 回答