【问题标题】:android SQLiteOpenHelper and Leaksandroid SQLiteOpenHelper 和泄漏
【发布时间】:2011-07-23 10:37:51
【问题描述】:

我想用一些额外的方法(比如 getStreetsCursor)来制作 SQLiteOpenHelper,这些方法从我的数据库中返回数据。所以我写了这样的东西:

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

在 DBHelper 中定义了几种方法,例如 getStreetsCursor(getAddresses、getAddress4 等)。 我想如果它是一个 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() 才真正执行。所有下一个调用都返回 null。那是由于 dbReadable.close();线。如果我删除它一切正常,但我再次泄漏。 所以我无法弄清楚出了什么问题。在每个自定义方法中,我都得到了 SQLiteDatabase dbReadable=this.getReadableDatabase();该行应该返回一个可读的实例,但在执行 close() 方法后它没有。 我猜它与我的自定义方法有关,因为它们在 DBHelper 的实例中调用 .getReadableDatabase()。如果我将这些方法直接放在活动中,一切正常 - 没有泄漏异常,并且每次方法返回正确的数据。但我想将这些方法放在我的 DBHelper 类中。

所以主要问题是 - 出了什么问题以及如何正确地做到这一点?

【问题讨论】:

    标签: android sqlite memory-leaks


    【解决方案1】:

    您不应该在每个方法 getSomethingCursor 中都有 SQLiteDatabase dbReadable=this.getReadableDatabase(); ,因为请求数据库对象的成本很高(我想我是在 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 上关闭它

    【讨论】:

    • 谢谢。但是如果它是第一次运行,我的意思是还没有创建数据库。我想我只是不能在构造函数中使用 dbReadable=this.getReadableDatabase() 就是这种情况......
    • 我不应该使用 finalize 而不是创建 closeDb 吗?
    • 在你调用你的方法之前,你必须实例化你的类。因此,当您实例化它时,您将同时获得 SQLiteDatabase 对象。
    • 如果数据库尚未创建。getReadable 返回 null。
    • 所以现在我必须检查我的每个方法中的 dbReadable 是否不为空。但无论如何,解决方案似乎是一个很好的解决方案。并感谢 startManagingCursor 通知。
    【解决方案2】:

    SQLiteDatabase dbReadable 设为类成员并实现

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

    那么,当你在activity的onDestroy()函数中调用mDBHelper.close();时,应该没问题。

    【讨论】:

      【解决方案3】:

      创建一个新函数

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

      然后您可以在onDestroy() fucntion 中的活动中调用它。

      【讨论】:

        【解决方案4】:

        多年后...我只想声明,现在我会更好地使用 Realm.io DB 引擎,并且我现在在我的项目中使用它。至于我的老问题,我还想注意使用getReadableDatabase() 是一个坏主意,因为跨设备碎片我遇到了一些古玩的sqlite错误 - 可读实例将在查询执行后自动关闭,这是意外行为。所以我建议始终使用getWritableDatabase() 方法,即使是为了阅读目的。最后我得到了Dmytro Danylyk solution - give it a try, Dmytro is 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 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-31
          • 1970-01-01
          相关资源
          最近更新 更多