尽管所有内容都已关闭,但SQLite连接泄漏了。

41

我发现很多东西像是 close the connectionclose the cursor,但我都已经做了这些事情。 仍然存在SQLite连接泄漏的问题,我收到了如下的警告:

A SQLiteConnection object for database was leaked!

我有一个数据库管理器,我在我的活动中使用以下代码调用:

DatabaseManager dbm = new DatabaseManager(this);

下面是我的数据库管理器类的代码:

public class DatabaseManager {

    private static final int DATABASE_VERSION = 9;
    private static final String DATABASE_NAME = "MyApp";
    private Context context = null;
    private DatabaseHelper dbHelper = null;
    private SQLiteDatabase db = null;


    public static class DatabaseHelper extends SQLiteOpenHelper {

         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }

         @Override
         public void onCreate(SQLiteDatabase db) {

                   //create database tables
         }

         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                      //destroy and recreate them
         }

     }

     public DatabaseManager(Context ctx) {
         this.context = ctx;
     }

    private DatabaseManager open() throws SQLException {
        dbHelper = new DatabaseHelper(context);
        db = dbHelper.getWritableDatabase();

        if (!db.isReadOnly()) {
            db.execSQL("PRAGMA foreign_keys = ON;");
        }

        return this;
    }

    private void close() {
        dbHelper.close();
    }
}

当我调用数据库方法时,我会执行以下操作:

public Object getData() {

    open();

            //... database operations take place ...

    close();

    return data;
}

但如我所说,我仍然收到了SQLite连接泄漏的警告。

我做错了什么?


1
我认为你也应该调用db.close()。 - user1525382
4
为什么DatabaseHelper要是静态的?这可能会导致资源泄漏,因为在没有对DatabaseHelper的引用时,它仍然持有到数据库的连接。 - MalaKa
1
我已经移除了static,但不幸的是它仍然泄漏。 - flp
close(); 行是否总是在成功执行 open(); 行后执行?您是否记录异常或只是忽略它们? - iCantSeeSharp
你应该在finally块中关闭资源,例如将db.close()放在finally块中。 - Igor Čordaš
显示剩余3条评论
3个回答

147
加粗的字体在引用中对应于您代码中的这部分:
private DatabaseManager open() throws SQLException {
    dbHelper = new DatabaseHelper(context);
    db = dbHelper.getWritableDatabase();

来源: http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html

方法一: 使用抽象工厂来实例化SQLiteOpenHelper

将数据库帮助程序声明为静态实例变量,并使用抽象工厂模式来保证单例属性。下面的示例代码应该会让您对如何正确设计DatabaseHelper类有一个很好的了解。

静态工厂getInstance方法确保在任何给定时间只会存在一个DatabaseHelper。如果mInstance对象尚未初始化,则将创建一个对象。如果已经创建了一个对象,则仅返回它。

不应使用new DatabaseHelper(context)来初始化您的辅助对象。
相反,始终使用DatabaseHelper.getInstance(context),因为它保证整个应用程序生命周期中只存在一个数据库帮助程序。

public static class DatabaseHelper extends SQLiteOpenHelper { 

  private static DatabaseHelper mInstance = null;

  private static final String DATABASE_NAME = "database_name";
  private static final String DATABASE_TABLE = "table_name";
  private static final int DATABASE_VERSION = 1;

  public static DatabaseHelper getInstance(Context ctx) {

    // Use the application context, which will ensure that you 
    // don't accidentally leak an Activity's context.
    // See this article for more information: http://bit.ly/6LRzfx
    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(ctx, DATABASE_NAME, null, DATABASE_VERSION);
  }
}

1
似乎解决了问题。我会进一步调查。 - flp
1
很棒的答案。在使用多个IntentService并同时从两个不同的数据库中工作时,我遇到了这个问题。我使用了这个答案,除了使用了两个单独的工厂方法。清除了所有的内存泄漏错误。执行时间增加了半秒或两秒,可能是因为我不再同时打开多个数据库实例。 - Jason Kennaly
1
我认为@tdmsoares对这个答案所做的最后一次编辑是错误的,因为DatabaseHelper是一个静态嵌套类,可以被实例化。或者我错过了什么需要注意的地方吗? - mcserep
3
在Android Studio中,我收到了这个警告:“不要将Android上下文类放在静态字段中;这会导致内存泄漏(并且也会破坏即时运行)。” - goetz
1
如果Android Studio抱怨“不要将Android上下文类放入静态字段中……”只要我们使用applicationContext()就可以忽略它,我们应该没问题。[请参见此SO答案] (https://dev59.com/R1oU5IYBdhLWcg3wM08H#40235834) - Jeel Shah
显示剩余5条评论

3
上面接受答案的完整例子如下: 它可能会对某些人有所帮助。
辅助类:
public class DatabaseHelper extends SQLiteOpenHelper {

private static final String DATABASE_NAME = "sample.db";
private static final int DATABASE_VERSION = 1;

private static DatabaseHelper mInstance;

private DatabaseHelper(@Nullable Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

public static synchronized DatabaseHelper getInstance(Context context) {

    if (mInstance == null) {
        mInstance = new DatabaseHelper(context.getApplicationContext());
    }
    return mInstance;
}

@Override
public void onCreate(SQLiteDatabase db) {

    // create table stuff


}

@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {

 // drop table stuff

    onCreate(db);
 }
}

活动:

SQLiteDatabase database = DatabaseHelper.getInstance(getApplicationContext()).getWritableDatabase();

Cursor cursor = database.query("query");

if (cursor != null) {
   while (cursor.moveToNext()) {
    // stuff
     }
   cursor.close();
   database.close();
 }

你能帮忙解决这个问题吗? - MrinmoyMk

1
private void method() {
        Cursor cursor = query();
        if (flag == false) {  // WRONG: return before close()
            return;
        }
        cursor.close();
   }

良好的实践应该像这样:

Good practice should be like this:

    private void method() {
        Cursor cursor = null;
        try {
            cursor = query();
        } finally {
            if (cursor != null)
                cursor.close();  // RIGHT: ensure resource is always recovered
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接