Нормально ли иметь один экземпляр SQLiteOpenHelper общим для всех действий в приложении Android?

было бы нормально иметь один экземпляр SQLiteOpenHelper в качестве члена подкласса приложения и иметь все действия, которым нужен экземпляр SQLiteDatabase, получить его от одного помощника?

5 ответов


один SQLiteOpenHelper экземпляр может помочь в случаях резьбы. Поскольку все потоки будут делиться общим SQLiteDatabase синхронизация операций.

однако я бы не стал делать подкласс Application. Просто иметь статический член данных, который является вашим SQLiteOpenHelper. Оба подхода дают вам что-то доступное отовсюду. Однако, есть только один подкласс Application, что делает его более трудным для вас, чтобы использовать другое подклассы Application (например, GreenDroid требует одного IIRC). Использование статического элемента данных позволяет избежать этого. Однако используйте Application Context при создании экземпляра этого static SQLiteOpenHelper (параметр конструктора), поэтому вы не пропускаете некоторые другие Context.

и, в случаях, когда вы не имеете дело с несколькими потоками, вы можете избежать возможных проблем с утечкой памяти только с помощью одного SQLiteOpenHelper экземпляр на компонент. Однако, на практике, вы должны иметь дело с несколькими потоками (например,, а Loader), поэтому эта рекомендация актуальна только для тривиальных приложений, таких как те, которые встречаются в некоторых книгах... :-)


Нажмите здесь, чтобы увидеть мой пост в блоге на эту тему.


CommonsWare включен (как обычно). Расширяя свой пост, вот пример кода, который иллюстрирует три возможных подхода. Это позволит получить доступ к базе данных во всем приложении.

подход #1: подклассы `приложения`

если вы знаете, что ваше приложение не будет очень сложным (т. е. если вы знаете, что у вас будет только один подкласс Application), то вы можете создать подкласс Application и есть ваша основная деятельность, расширить его. Это гарантирует, что один экземпляр базы данных работает на протяжении всего жизненного цикла приложения.

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: абстрактная база данных SQLite с "ContentProvider"

это подход, который я бы предложил. Во-первых, новый LoaderManager класс сильно зависит от ContentProviders, поэтому, если вы хотите реализовать действие или фрагмент LoaderManager.LoaderCallbacks<Cursor> (который я предлагаю вам воспользоваться, это волшебно!), вам нужно будет реализовать ContentProvider для вашего приложения. Кроме того, вам не нужно беспокоиться о создании Синглтона помощник по базам данных с ContentProviders. Просто позвоните getContentResolver() от деятельности и система позаботится обо всем для вас (другими словами, нет необходимости в разработке Одноэлементного шаблона, чтобы предотвратить создание нескольких экземпляров).

надеюсь, что это поможет!


Я написал MultiThreadSQLiteOpenHelper, который является улучшенным SQLiteOpenHelper для приложений Android, где несколько потоков могут открывать и закрывать одну и ту же базу данных sqlite.

вместо вызова метода close, threads попросите закрыть база данных, препятствующая потоку выполнять запрос в закрытой базе данных.

Если каждый поток попросил закрытия, то закрытие фактически выполняется. Каждое действие или поток (ui-thread и user-threads) выполняет открытый вызов базы данных при возобновлении и запрашивает закрытие базы данных при приостановке или завершении.

исходный код и образцы доступны здесь: https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper


Я провел много исследований по этой теме, и я согласен со всеми пунктами, упомянутыми 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.

Если вы попытаетесь записать в базу данных из фактических различных соединений одновременно, это не удастся. Это не будет ждать, пока первый будет сделан, а затем написать. Он просто не напишет ваше изменение. Хуже того, если вы не вызываете правильную версию insert/update на SQLiteDatabase, вы не получите исключения. Вы просто получите сообщение в своем LogCat, и это будет все.

первая проблема, реальные, различные соединения. Самое замечательное в открытом исходном коде - вы можете копать прямо и видеть, что происходит. Класс SQLiteOpenHelper делает некоторые забавные вещи. Хотя есть способ получить соединение с базой данных только для чтения, а также соединение для чтения и записи, под капотом всегда одно и то же соединение. Предполагая, что нет ошибок записи файлов, даже соединение только для чтения действительно одиночное соединение чтения и записи. Очень смешно. Итак, если вы используете один вспомогательный экземпляр в своем приложении, даже из нескольких потоков, вы никогда не действительно через несколько соединений.

кроме того, класс SQLiteDatabase, из которого каждый помощник имеет только один экземпляр, реализует блокировку уровня java на себе. Таким образом, когда вы фактически выполняете операции с базой данных, все остальные операции БД будут заблокированы. Итак, даже если у вас есть несколько потоков, делающих вещи, если вы делаете это увеличьте производительность базы данных, у меня для вас плохие новости. Никакая выгода.

Интересные Наблюдения

Если вы отключите один поток записи, поэтому только один поток записывается в БД, но другое чтение, и оба имеют свои собственные соединения, производительность чтения увеличивается и Я не вижу никаких проблем с блокировкой. что-то продолжать. Я еще не пробовал это с write batching.

Если вы собираетесь выполнять более чем одно обновление любого рода, оберните его в транзакцию. Похоже, что обновления 50, которые я делаю в транзакции, занимают столько же времени, сколько обновление 1 вне транзакции. Я предполагаю, что за пределами вызовов транзакций каждое обновление пытается записать изменения БД на диск. Внутри транзакции записи выполняются в одном блоке, а накладные расходы на запись затмевают саму логику обновления.


да, именно так вы должны это сделать, имея вспомогательный класс для действий, которым нужен экземпляр базы данных.