我的一个内容提供商管理多个数据库,相同的架构具有不同的数据集。为了防止 IllegalStateException 在垃圾收集发现我的内容提供程序中有一个不再有任何引用它的开放数据库时通过,即使 SQLiteCursor 确实给我留下了几个选择:
1) 让 SQLiteDatabase 对象保持打开状态并将其放入一个集合中,不再使用。
2) 让 SQLiteDatabase 对象保持打开状态并开发一个缓存,让我在访问同一个数据库时可以重用数据库对象。
3) 游标关闭时关闭数据库。
解决方案 1 违背了我的更好判断。解决方案一只是资源泄漏的另一种形式;它的一个可取之处是它不会扰乱系统。我立即排除了这个选择。
解决方案 2 是我认为的最佳解决方案。它节省了资源,同时通过不必重新打开数据库连接来减少运行时间。这个解决方案的缺点是我必须写缓存,它会增加应用程序的大小。大小真的不是问题,但写它的时间是。我暂时传递了这个解决方案,以后可能会回来。
解决方案 3 是决定采用的解决方案。首先,我认为这样做很简单;在活动中,我需要做的就是将我的 Content Provider 返回的光标重新转换为 SQLiteCursor。然后我可以调用它的 getDatabase() 方法并在数据库上调用 close()。
这是不可能的。事实证明,从 Content Provider 返回的游标位于阻止直接访问实际 Cursor 对象的包装类中。包装器将它接收到的方法调用委托给 Cursor。没有机会将光标转换回其派生类型。
因此,我没有将关闭数据库的责任放在活动上,而是选择了一条不同的更简单的路线。我通过扩展 SQLiteCursor 类派生了我自己的 Cursor 类。我在派生类中添加了两个数据成员;一个用于引用数据库,另一个是用于调试的 ID。该类的 ctor 与 SQLiteCursor ctor 具有相同的签名,并在末尾添加了一个额外的参数来设置 ID 值。 ctor 设置数据库数据成员以确保如果在游标关闭之前发生垃圾收集,则某些内容正在引用数据库。
我重写了 close() 方法,以便它会关闭游标 [super.close()] 并在引用不为 null 时关闭数据库。
我还覆盖了 toString() 方法,以便将 ID 号附加到字符串中。这使我能够在日志文件中跟踪哪些游标仍处于打开状态以及哪些游标已打开和关闭。
我还添加了一个 closeForReuse() 方法,以便内容提供者可以为多个查询重用一个数据库,而不必每次都打开一个新的数据库连接。
我还创建了一个实现 SQLiteDatabase.CursorFactory 接口的类。它创建了我的新派生游标类并管理传递给每个游标的 ID 值。
此解决方案仍然要求每个活动在使用游标完成时关闭传递给它的游标。由于这是一种良好的编程习惯,因此不必担心。