Android SQLiteOpenHelper и утечки - PullRequest
       0

Android SQLiteOpenHelper и утечки

1 голос
/ 26 марта 2011

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

public class DBHelper extends SQLiteOpenHelper { 
    public static final String DB_NAME="some.db"; 
    public static final String T1_NAME="streets"; 
    public static final String T1_FNAME1="name"; 
    public static final String T2_NAME="addresses"; 
    public static final String T2_FNAME1="name"; 
    public static final String T2_FNAME2="address"; 

    private Context appContext;

  public DBHelper(Context context) { 
    super(context, DB_NAME, null, 1);
    appContext=context;
  }

  public Cursor getStreetsCursor(String chars) {
  SQLiteDatabase dbReadable=this.getReadableDatabase();
      Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
                new String[] {"_id",DBHelper.T1_FNAME1}, 
                DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
                null, null, null, DBHelper.T1_FNAME1);

    return curStreets;
  }

Существует несколько методов, таких как getStreetsCursor (getAddresses, getAddress4 и т. Д.), Определенных в DBHelper.Я предполагаю, что если это DB Helper , он определенно должен иметь такие методы, я имею в виду, что DBHelper является для них логическим заполнителем.

Что я делаю в этом упражнении, так это создаю новый экземпляр DBHelper ихраните его в приватном поле (называемом mDBHelper) деятельности.Кроме того, в методе действия onDestroy у меня есть mDBHelper.close ().

private DBHelper mDBHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDBHelper = new DBHelper(this);
...
    @Override
    protected void onDestroy() {
        if (mDBHelper!=null){
            mDBHelper.close();
            Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
        }
        super.onDestroy();
    }

Эти действия используют mDBHelper только одним способом - вызывая его пользовательские методы, такие как mDBHelper.getStreetsCursor ().В конце концов я нашел сообщение об исключении в logcat о том, что мое приложение получило утечки в таких действиях, которые используют DBHelper.Там написано что-то вроде «база данных никогда не была закрыта».Поэтому я решил добавить вызов метода close () в каждый из моих пользовательских методов непосредственно перед возвратом.Так это выглядит так:

      public Cursor getStreetsCursor(String chars) {
          SQLiteDatabase dbReadable=this.getReadableDatabase();
          Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
                    new String[] {"_id",DBHelper.T1_FNAME1}, 
                    DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
                    null, null, null, DBHelper.T1_FNAME1);

            dbReadable.close();
            return curStreets;
      }

Теперь у меня нет утечек, но возникла следующая проблема - действительно выполняется только первый вызов mDBHelper.getStreetsCursor ().Все последующие вызовы возвращают ноль.Это связано с dbReadable.close ();линия.Если я удаляю его, все работает нормально, но я снова получаю утечки.Так что я не могу понять, что происходит не так.В каждом пользовательском методе я получил SQLiteDatabase dbReadable = this.getReadableDatabase ();строка, которая должна возвращать читаемый экземпляр, но после выполнения метода close () это не так.Я думаю, это о моих пользовательских методах, потому что они вызывают .getReadableDatabase () внутри экземпляра DBHelper.Если я помещаю эти методы непосредственно в действие, все работает нормально - исключений утечки нет, и каждый раз методы возвращают правильные данные.Но я хочу разместить эти методы в моем классе DBHelper.

Итак, главный вопрос - что не так и как это сделать правильно?

Ответы [ 4 ]

5 голосов
/ 26 марта 2011

Вы не должны иметь SQLiteDatabase dbReadable=this.getReadableDatabase(); в каждом методе getSomethingCursor, так как запрос объекта базы данных стоит дорого (я думаю, что я прочитал его в SO).

Таким образом, вы можете создать объект из вашего конструктора

  SQLiteDatabase dbReadable;

  public DBHelper(Context context) { 
    super(context, DB_NAME, null, 1);
    appContext=context;
    dbReadable=this.getReadableDatabase()
  }


  public Cursor getStreetsCursor(String chars) {
          Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
                    new String[] {"_id",DBHelper.T1_FNAME1}, 
                    DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
                    null, null, null, DBHelper.T1_FNAME1);

            return curStreets;
  }

Создайте метод для закрытия дескриптора базы данных:

public closeDb() {
    if (dbReadable != null) { dbReadable.close();}
}

А в тебе активность:

    @Override
    protected void onDestroy() {
        if (mDBHelper!=null){
            mDBHelper.closeDb();
            Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
        }
        super.onDestroy();
    }

И используйте startManagingCursor (если SDK onDestroy, например

0 голосов
/ 08 августа 2016

Спустя годы ... Я просто хочу заявить, что теперь я лучше буду использовать движок Realm.io DB и делаю это сейчас в своих проектах.Что касается моего старого вопроса, я также хочу заметить, что использование getReadableDatabase() является плохой идеей, поскольку при фрагментации устройств я встречал некоторую странную ошибку sqlite - читаемый экземпляр будет автоматически закрыт сразу после выполнения запроса, что является неожиданным поведением.Поэтому я рекомендую всегда использовать метод getWritableDatabase() даже для чтения.И, наконец, я получаю решение Dmytro Danylyk - попробуйте, Dmytro - Google Developer Expert :

public class DatabaseManager {

    private AtomicInteger mOpenCounter = new AtomicInteger();

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        if(mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();

        }
    }
}

И используйте его следующим образом.

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();  
database.insert(...);  
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way 
0 голосов
/ 26 июля 2016

Создать новую функцию

public void DBclose()
{
curstreets.close();
dbReadable.close();
}

Тогда вы можете назвать это в своей деятельности в onDestroy() функция.

0 голосов
/ 26 марта 2011

Сделать SQLiteDatabase dbReadable членом класса и реализовать

public void close()
{
    dbReadable.close();
}

Затем, когда вы вызываете mDBHelper.close(); в функции onDestroy() вашей активности, все должно быть в порядке.

...