可以让 Android 应用程序中的所有活动共享一个 SQLiteOpenHelper 实例吗?

Posted

技术标签:

【中文标题】可以让 Android 应用程序中的所有活动共享一个 SQLiteOpenHelper 实例吗?【英文标题】:Is it OK to have one instance of SQLiteOpenHelper shared by all Activities in an Android application? 【发布时间】:2012-02-11 21:31:51 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

如何将上下文作为参数传递给扩展 sqliteopenhelper 的数据库助手类的构造函数?由于它因不同的活动而异,是否有我们可以作为参数传递的基本上下文?【参考方案2】:

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

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

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

【讨论】:

:) 我实际上使用了您的 LoaderEx 类和高级 android 书籍来学习如何将数据库访问移出 UI 线程。非常感谢你们俩。他们帮了大忙。 那么,如果所有类和线程都会到达同一个确切的实例,你什么时候应该调用“close”呢? @androiddeveloper:一般情况下,您不会拨打close(),除非您碰巧处于100% 确定拨打close() 安全的情况。 @androiddeveloper:“当应用程序以这种方式在后台运行时,它不会让操作系统“想要”更多地关闭进程吗?” ——我不知道。 “特别是因为这个类的真正引擎是用 C/C++ 编写的,它把内存使用放在堆之外?” -- 每个进程都包含 SQLite,就像在 zygote AFAIK 中一样。 AFAIK 的开放数据库的 RAM 并不是特别大。 @Sean:不,该进程刚刚终止。当这种情况发生时,你所有的内存、线程等等都会poof【参考方案3】:

Click here to see my blog post on this subject.


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

希望有帮助!

【讨论】:

我只是想指出 CommonsWare 的 LoaderEx 库显示了在直接使用 SQLite 数据库而不是 ContentProvider 时如何使用LoaderManager.LoaderCallbacks 接口和Loaders。 github.com/commonsguy/cwac-loaderex 您需要从覆盖中调用 super.onCreate()。 onTerminate() 可能相同 - 不确定。 哎呀,忘了...这两种方法都需要它。固定! 再想一想,onTerminate() 可能没有明确的答案,因为甚至可能不会调用该方法。包括它似乎更安全:) 为什么要分配`this.mCtx = ctx;`?【参考方案4】:

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

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

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

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

【讨论】:

你为什么不实现 getReadableDatabase() ?【参考方案5】:

我对这个话题做了很多研究,我同意 commonware 提到的所有观点。但我认为这里每个人都缺少一个重要的点,这个问题的答案完全取决于您的用例,所以如果您的应用程序通过 multiple threads 读取数据库并且仅使用 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 次更新所用的时间相同。我的猜测是,在事务调用之外,每次更新都会尝试将数据库更改写入磁盘。在事务内部,写入是在一个块中完成的,写入的开销使更新逻辑本身相形见绌。

【讨论】:

伟大的研究。感谢分享。 感谢@Julian,所以这里的推论是,如果您的数据库只是读取,请不要使用 SINGLETON 但创建不同的实例,因为这将允许并发访问,因为单个实例会在读取时阻止所有其他操作一个数据库,如果您需要多个阅读器和一个编写器,请使用具有单独实例的 WAL 功能,因此我认为不应使用 SINGLETON

以上是关于可以让 Android 应用程序中的所有活动共享一个 SQLiteOpenHelper 实例吗?的主要内容,如果未能解决你的问题,请参考以下文章

asmack + android + 聊天应用程序

为应用程序中的所有活动应用 android:excludeFromRecents

Android Fragment

在所有其他 Android 应用之上显示应用视图

如何优雅地关闭 Android 应用程序中的所有活动并关闭所有正在运行的线程?

Android学习之基础知识七—碎片的使用