Android 应用程序中的所有 Activity 共享一个 SQLiteOpenHelper 实例是否可以?

发布于 2024-12-27 11:54:33 字数 85 浏览 1 评论 0原文

将 SQLiteOpenHelper 的单个实例作为子类应用程序的成员,并且让所有需要 SQLiteDatabase 实例的活动从一个帮助器获取它是否可以?

Would it be OK to have a single instance of SQLiteOpenHelper as a member of a subclassed Application, and have all Activities that need an instance of SQLiteDatabase get it from the one helper?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

﹏半生如梦愿梦如真 2025-01-03 11:54:33

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


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(我建议您利用,这太神奇了!),您需要为您的应用程序实现一个 ContentProvider。此外,您无需担心使用 ContentProviders 创建 Singleton 数据库助手。只需从 Activity 中调用 getContentResolver() ,系统就会为您处理好一切(换句话说,无需设计 Singleton 模式来防止创建多个实例)。

希望有帮助!

Click here to see my blog post on this subject.


CommonsWare is right on (as usual). Expanding on his post, here is some sample code that illustrates three possible approaches. These will allow access to the database throughout the application.

Approach #1: subclassing `Application`

If you know your application won't be very complicated (i.e. if you know you'll only end up having one subclass of Application), then you can create a subclass of Application and have your main Activity extend it. This ensures that one instance of the database is running throughout the Application's entire life cycle.

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;
    }
}

Approach #2: have `SQLiteOpenHelper` be a static data member

This isn't the complete implementation, but it should give you a good idea on how to go about designing the DatabaseHelper class correctly. The static factory method ensures that there exists only one DatabaseHelper instance at any time.

/**
 * 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;
    }
}

Approach #3: abstract the SQLite database with a `ContentProvider`

This is the approach I would suggest. For one, the new LoaderManager class relies heavily on ContentProviders, so if you want an Activity or Fragment to implement LoaderManager.LoaderCallbacks<Cursor> (which I suggest you take advantage of, it is magical!), you'll need to implement a ContentProvider for your application. Further, you don't need to worry about making a Singleton database helper with ContentProviders. Simply call getContentResolver() from the Activity and the system will take care of everything for you (in other words, there is no need for designing a Singleton pattern to prevent multiple instances from being created).

Hope that helps!

蹲墙角沉默 2025-01-03 11:54:33

拥有一个SQLiteOpenHelper实例可以在线程情况下有所帮助。由于所有线程将共享公共SQLiteDatabase,因此提供了操作同步。

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

而且,如果您不处理多个线程,您只需为每个组件使用一个 SQLiteOpenHelper 实例即可避免任何可能的内存泄漏问题。然而,在实践中,您应该处理多个线程(例如,Loader),因此此建议仅与琐碎的应用程序相关,例如在某些书籍中找到的应用程序...:-)

Having a single SQLiteOpenHelper instance can help in threading cases. Since all threads would share the common SQLiteDatabase, synchronization of operations is provided.

However, I wouldn't make a subclass of Application. Just have a static data member that is your SQLiteOpenHelper. Both approaches give you something accessible from anywhere. However, there is only one subclass of Application, making it more difficult for you to use other subclasses of Application (e.g., GreenDroid requires one IIRC). Using a static data member avoids that. However, do use the Application Context when instantiating this static SQLiteOpenHelper (constructor parameter), so you do not leak some other Context.

And, in cases where you aren't dealing with multiple threads, you can avoid any possible memory leak issues by just using one SQLiteOpenHelper instance per component. However, in practice, you should be dealing with multiple threads (e.g., a Loader), so this recommendation is only relevant for trivial applications, such as those found in some books... :-)

桃扇骨 2025-01-03 11:54:33

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

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

如果每个线程都要求关闭,则实际执行关闭。每个活动或线程(ui 线程和用户线程)在恢复时对数据库执行打开调用,并在暂停或完成时请求关闭数据库。

源代码和示例可在此处获取:
https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

I have written MultiThreadSQLiteOpenHelper which is an enhanced SQLiteOpenHelper for Android applications where several threads might open and close the same sqlite database.

Instead of calling close method, threads ask for closing the database, preventing a thread from performing a query on a closed database.

If each thread asked for closing, then a close is actually performed. Each activity or thread (ui-thread and user-threads) performs an open call on database when resuming, and asks for closing the database when pausing or finishing.

Source code and samples available here:
https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

自找没趣 2025-01-03 11:54:33

我对这个主题做了很多研究,我同意 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 次更新花费的时间相同。我的猜测是,在事务调用之外,每次更新都会尝试将数据库更改写入磁盘。在事务内部,写入是在一个块中完成的,写入的开销使更新逻辑本身相形见绌。

I did a lot of research on this topic and I agree with all the points mentioned by commonware . But i think there is an important point everyone is missing here , Answer to this question is entirely dependent on your Use Case so if your application is reading databases via multiple threads and only reading using Singleton has a huge performance hit as all the functions are synchronized and are executed serially as there is a single connection to database
Open source is great, by the way. You can dig right into the code and see what’s going on. From that and some testing, I’ve learned the following are true:

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.

If you try to write to the database from actual distinct connections at the same time, one will fail. It will not wait till the first is done and then write. It will simply not write your change. Worse, if you don’t call the right version of insert/update on the SQLiteDatabase, you won’t get an exception. You’ll just get a message in your LogCat, and that will be it.

The first problem, real, distinct connections. The great thing about open source code is you can dig right in and see what’s going on. The SQLiteOpenHelper class does some funny things. Although there is a method to get a read-only database connection as well as a read-write connection, under the hood, its always the same connection. Assuming there are no file write errors, even the read-only connection is really the single, read-write connection. Pretty funny. So, if you use one helper instance in your app, even from multiple threads, you never really using multiple connections.

Also, the SQLiteDatabase class, of which each helper has only one instance, implements java level locking on itself. So, when you’re actually executing database operations, all other db operations will be locked out. So, even if you have multiple threads doing stuff, if you’re doing it to maximize database performance, I have some bad news for you. No benefit.

Interesting Observations

If you turn off one writing thread, so only one thread is writing to the db, but another reading, and both have their own connections, the read performance shoots WAY up and I don’t see any lock issues. That’s something to pursue. I have not tried that with write batching yet.

If you are going to perform more than one update of any kind, wrap it in a transaction. It seems like the 50 updates I do in the transaction take the same amount of time as the 1 update outside of the transaction. My guess is that outside of the transaction calls, each update attempts to write the db changes to disk. Inside the transaction, the writes are done in one block, and the overhead of writing dwarfs the update logic itself.

罪歌 2025-01-03 11:54:33

是的,这就是您应该采取的方式,为需要数据库实例的活动提供一个帮助程序类。

Yes, that is the way you should go about it, having a helper class for the activities that need an instance of the Database.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文