java Android Content Provider 数据库泄露问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1379200/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 16:21:02  来源:igfitidea点击:

Android Content Provider database leak issue

javaandroid

提问by MattC

I am writing a content provider for this application and in my content provider I am opening a database connection, running a query and returning the cursor of results to the calling program. If I close this database connection in the provider, the cursor has no results. If I leave it open, I get "leak found" errors in my DDMS log. What am I missing here? What's the clean, proper way to return a cursor of database results?

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

回答by CommonsWare

You're not missing anything AFAIK. Android is missing an onDestroy()(or the equivalent) for ContentProvider. There isn't even anything in the source code in this area that suggests there is some sort of onDestroy()that just isn't surfaced in the SDK.

你没有错过任何 AFAIK。Android 缺少一个onDestroy()(或等效的) for ContentProvider. 这方面的源代码中甚至没有任何内容表明onDestroy()SDK 中没有出现某种类型的内容。

If you look at the source code for AlarmProviderand LauncherProvider, they even create database objects on a per-API-call basis (e.g., every time they get insert(), they open a writable database handle that they never close).

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

回答by Steven F. Le Brun

One of my content providers manages multiple databases, same schema with different data sets. To prevent the IllegalStateException being through when garbage collection discovered that there was an open database in my content provider that no longer had anything referencing it, even though the SQLiteCursor does left me with several choices:

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

1) Leave the SQLiteDatabase object open and place it into a collection, never to be used again.

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

2) Leave the SQLiteDatabase object open and develop a cache that would allow me to reuse the database object when accessing the same database.

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

3) Close the database when the cursor is closed.

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

Solution 1goes against my better judgement. Solution one is just another form of a resource leak; its one saving grace is that it does not upset the system. I ruled this choice out immediately.

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

Solution 2was my idea of the best solution. It conserved resources at the same time reducing run-time by not having to reopen database connections. The down side of this solution was that I would have to write the cache and it would have increased the size of the application. Size really was not an issue but the time to write it was. I passed on this solution for the present time and may come back to it later.

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

Solution 3is the one that decided to go with. First, I thought it would be simple to do; in the activity, all I needed to do was recast the Cursor returned by my Content Provider back into a SQLiteCursor. Then I could invoke its getDatabase() method and invoke close() on the database.

解决方案 3是决定采用的解决方案。首先,我认为这很简单;在活动中,我需要做的就是将内容提供程序返回的 Cursor 重新转换为 SQLiteCursor。然后我可以调用它的 getDatabase() 方法并在数据库上调用 close() 。

This is not possible. It turns out that the cursor returned from the Content Provider is within a wrapper class that prevents direct access to the actual Cursor object. The wrapper delegates method calls it receives to the Cursor. No chance to cast the cursor back to its derived type.

这不可能。事实证明,从内容提供者返回的游标位于一个包装类中,该类阻止了对实际 Cursor 对象的直接访问。包装器委托方法调用它接收到 Cursor。没有机会将光标转换回其派生类型。

So, instead of placing the responsibility of closing the database on the activity, I went a different and easier route. I derived my own Cursor class by extending the SQLiteCursor class. I added two data members to my derived class; one to reference the database and the other was a ID for use in debugging. The class's ctor had the same signature as the SQLiteCursor ctor with an extra parameter added to the end for setting the ID value. The ctor set the database data member to insure that something was referencing the database if a garbage collection occurred before the cursor was closed.

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

I overrode the close() method so that it would both close the cursor [super.close()] and close the database if the reference was not null.

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

I also overrode the toString() method so that the ID number was appended to the string. This allowed me to track which cursors were still open and which ones have been opened and closed in the Log file.

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

I also added a method closeForReuse() so that the Content Provider could reuse a database for multiple queries without having to open a new database connection each time.

我还添加了一个方法 closeForReuse() 以便内容提供者可以为多个查询重用一个数据库,而不必每次都打开一个新的数据库连接。

I also created a class that implemented the SQLiteDatabase.CursorFactory interface. It created my new derived cursor class and managed the ID value passed to each one.

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

This solution still requires that every activity closes the cursors passed to it when they are done using the cursor. Since this is good programming practice, it was not a concern.

该解决方案仍然要求每个活动在使用游标完成时关闭传递给它的游标。由于这是良好的编程习惯,因此不必担心。

回答by Chiwai Chan

It is perfectly fine by leaving the database connection opened throughout the entire runtime of your app, you just have to make sure you close the cursor after once you're done with it.

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

I presume you're querying and using the cursors in an Activity? if so make sure you are closing the cursors by calling the cursor.close();method, I notice if you're not closing the cursors in an Activity and then moving onto another Activity that you will get these leak messages when running another query.

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

I find that it's best practice to override the onDestroy method in your activity and close all the cursors in it.

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