Можно ли использовать один экземпляр SQLiteOpenHelper для всех операций в приложении Android? - PullRequest
39 голосов
/ 17 января 2012

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

Ответы [ 5 ]

46 голосов
/ 17 января 2012

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


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

Подход № 1: создание подкласса `Application`

Если вы знаете, что ваше приложение не будет очень сложным (т.е. если вы знаете, что у вас будет только один подкласс 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, поэтому, если вы хотите, чтобы Activity или Fragment реализовали LoaderManager.LoaderCallbacks<Cursor> (что я предлагаю вам использовать в своих интересах, это волшебно!), Вам нужно реализовать ContentProvider для вашего приложения.Кроме того, вам не нужно беспокоиться о создании помощника базы данных Singleton с помощью ContentProviders.Просто позвоните getContentResolver() из Упражнения, и система позаботится обо всем за вас (другими словами, нет необходимости разрабатывать шаблон Singleton для предотвращения создания нескольких экземпляров).

Надежда, которая помогает!

41 голосов
/ 17 января 2012

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

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

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

7 голосов
/ 15 марта 2012

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

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

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

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

4 голосов
/ 03 июля 2015

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

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

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

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

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

Если вы отключите один поток записи, так что только один поток записывает в БД, но другое чтение, и оба имеют свои собственные подключения, производительность чтения возрастает ПУТЬ и Я не вижу проблем с блокировкой . Это то, что нужно преследовать. Я еще не пробовал это с пакетной записью.

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

1 голос
/ 17 января 2012

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

...