Android Content Provider数据库泄漏问题

时间:2009-09-04 13:18:21

标签: java android

我正在为此应用程序编写内容提供程序,在我的内容提供程序中,我打开数据库连接,运行查询并将结果的光标返回给调用程序。如果我在提供程序中关闭此数据库连接,则游标没有结果。如果我打开它,我的DDMS日志中会出现“泄漏发现”错误。我在这里错过了什么?什么是返回数据库结果光标的干净,正确的方法?

3 个答案:

答案 0 :(得分:13)

你没有错过任何AFAIK。 Android缺少onDestroy()的{​​{1}}(或等效内容)。此区域的源代码中甚至没有任何内容表明存在某种ContentProvider,而这些内容并未在SDK中显示。

如果查看onDestroy()AlarmProvider的源代码,他们甚至可以基于每个API调用创建数据库对象(例如,每次获得LauncherProvider时,他们都会打开一个他们永远不会关闭的可写数据库句柄。

答案 1 :(得分:11)

我的一个内容提供商管理多个数据库,相同的架构使用不同的数据集。为了防止在垃圾收集发现我的内容提供者中有一个打开的数据库不再引用它的任何东西时IllegalStateException通过,即使SQLiteCursor给我留下了几个选择:

1)保持SQLiteDatabase对象打开并将其放入集合中,永远不要再使用。

2)保持SQLiteDatabase对象打开并开发一个缓存,允许我在访问同一个数据库时重用数据库对象。

3)关闭光标时关闭数据库。

解决方案1 ​​违背我的判断。解决方案之一只是资源泄漏的另一种形式;它的一个优点是它不会扰乱系统。我立即排除了这个选择。

解决方案2 是我对最佳解决方案的想法。它通过不必重新打开数据库连接来节省资源,同时减少了运行时间。这个解决方案的缺点是我必须编写缓存,它会增加应用程序的大小。大小真的不是问题,但写的时间是。我现在传递了这个解决方案,可能会在以后再回来。

解决方案3 是决定与之合作的人。首先,我认为做起来很简单;在活动中,我需要做的就是将我的Content Provider返回的Cursor重新转换回SQLiteCursor。然后我可以调用它的getDatabase()方法并在数据库上调用close()。

这是不可能的。事实证明,从Content Provider返回的游标位于一个包装类中,该类阻止直接访问实际的Cursor对象。包装器将它接收的方法调用委托给Cursor。没有机会将光标强制转换为派生类型。

因此,我没有把关闭数据库的责任放在活动上,而是采用了另一种更简单的方法。我通过扩展SQLiteCursor类来派生自己的Cursor类。我在派生类中添加了两个数据成员;一个用于引用数据库,另一个是用于调试的ID。类的ctor与SQLiteCursor ctor具有相同的签名,并在末尾添加了一个额外的参数来设置ID值。如果在光标关闭之前发生了垃圾收集,ctor会设置数据库数据成员以确保某些内容正在引用数据库。

我覆盖了close()方法,以便在引用不为空时关闭游标[super.close()]并关闭数据库。

我还覆盖了toString()方法,以便将ID号附加到字符串中。这使我能够跟踪哪些游标仍然打开以及哪些游标已在日志文件中打开和关闭。

我还添加了一个方法closeForReuse(),以便Content Provider可以将数据库重用于多个查询,而无需每次都打开新的数据库连接。

我还创建了一个实现SQLiteDatabase.CursorFactory接口的类。它创建了我的新派生游标类,并管理传递给每个游标的ID值。

此解决方案仍然要求每个活动在使用游标完成时关闭传递给它的游标。由于这是一个很好的编程实践,因此不是一个问题。

答案 2 :(得分:5)

通过在应用程序的整个运行时间内打开数据库连接完全没问题,只需确保在完成后关闭光标。

我认为你在一个Activity中查询和使用游标?如果是这样,请确保通过调用cursor.close();方法关闭游标,我注意到如果您没有关闭活动中的游标,然后转到另一个活动,那么在运行另一个查询时您将收到这些泄漏消息。

我发现最佳做法是覆盖活动中的onDestroy方法并关闭其中的所有游标。