Introduction:
This article describes how to detect the Cursor leakage in Android and how to use it. It also points out several common error examples. Some leaks are hard to detect in the code, but exceptions will inevitably occur after the program runs for a long time. This method is also suitable for other scenarios that require resource leakage detection.
Recently, it was found that a vegetable mobile phone connection program encountered a severe Cursor Leakage When querying the MediaProvider database. After running for a period of time, all programs in the system that use the database could not be used. In addition, it is often found that some applications have a Cursor leakage phenomenon, because it takes a long time to run before exceptions occur, so some such bugs have not been detected for a long time.
However, once the Cursor leak reaches a certain number (usually hundreds of records), the database cannot be queried. The database service can be recovered only when the process of the database service is stopped and restarted. The following error message indicates that a pid program opened 866 Cursor but not closed, resulting in exception:Copy codeThe Code is as follows: 3634 3644 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported into SS processes .)
3634 3644 E JavaBinder: android. database. cursorrentwallocationexception: Cursor window allocation of 2048 kb failed. # Open Cursors = 866 (# cursors opened by pid 1565 = 866)
3634 3644 E JavaBinder: at android. database. CursorWindow. (CursorWindow. java: 104)
3634 3644 E JavaBinder: at android. database. abstract=wedcursor. clearOrCreateWindow (abstract=wedcursor. java: 198)
3634 3644 E JavaBinder: at android. database. sqlite. SQLiteCursor. fillWindow (SQLiteCursor. java: 147)
3634 3644 E JavaBinder: at android. database. sqlite. SQLiteCursor. getCount (SQLiteCursor. java: 141)
3634 3644 E JavaBinder: at android. database. CursorToBulkCursorAdaptor. getBulkCursorDescriptor (CursorToBulkCursorAdaptor. java: 143)
3634 3644 E JavaBinder: at android. content. ContentProviderNative. onTransact (ContentProviderNative. java: 118)
3634 3644 E JavaBinder: at android. OS .Binder.exe cTransact (Binder. java: 367)
3634 3644 E JavaBinder: at dalvik. system. NativeStart. run (Native Method)
1. Cursor Detection Principle
When the Cursor object is recycled by JVM and run to the finalize () method, check whether the close () method is called. This method is also applied in ContentResolver. The simplified sample code is as follows:Copy codeThe Code is as follows: import android. database. Cursor;
Import android. database. CursorWrapper;
Import android. util. Log;
Public class TestCursor extends CursorWrapper {
Private static final String TAG = "TestCursor ";
Private boolean mIsClosed = false;
Private Throwable mTrace;
Public TestCursor (Cursor c ){
Super (c );
MTrace = new Throwable ("Explicit termination method 'close () 'not called ");
}
@ Override
Public void close (){
MIsClosed = true;
}
@ Override
Public void finalize () throws Throwable {
Try {
If (mIsClosed! = True ){
Log. e (TAG, "Cursor leaks", mTrace );
}
} Finally {
Super. finalize ();
}
}
}
Then, TestCursor is returned to the APP as the query result during the query:
1 return new TestCursor (cursor); // cursor is the result of a common query, for example, from ContentProvider. query ()
This method is also applicable to all situations where explicit resource release methods need to be detected and are not called. It is a common method. However, in the finalize () method, note that
Advantages: Accurate. Because the resource is not released when the Cursor object is recycled, it must have been a resource leak.
Disadvantages: Depending on the finalize () method, it also depends on the JVM's garbage collection policy. For example, 10 Cursor objects in an APP are leaked, and the 10 objects are no longer referenced and pointed to as recyclable, but the JVM may not immediately recycle them (unpredictable time ), if you cannot find the problem. In addition, in some cases, even if the object is recycled finalize (), it may not be executed, that is, it cannot ensure that all problems are detected. For more information about finalize (), refer to Item 7: Avoid Finalizers in objective Java 2nd Edition.
2. Usage
For APP developers
Since GINGERBREAD, Android has provided the StrictMode tool to help developers check whether they have carefully performed some improper operations. The usage is to set StrictMode in the Activity. The following example shows how to enable the function of checking the leaked SQLite object and Closeable object (Common Cursor/FileInputStream, if any violation is found, log and force the program to exit.Copy codeThe Code is as follows: import android. OS. StrictMode;
Public class TestActivity extends Activity {
Private static final boolean pai_mode = true;
Public void onCreate (){
If (cmd_mode ){
StrictMode. setVMPolicy (new StrictMode. VMPolicy. Builder ()
. DetectLeakedSqlLiteObjects ()
. DetectLeakedClosableObjects ()
. PenaltyLog ()
. PenaltyDeath ()
. Build ());
}
Super. onCreate ();
}
}
For framework developers
If you provide database data through ContentProvider, The CloseGuard class in ContentResolver performs similar detection, but you need to enable it yourself (in the above example, CloseGuard is enabled ):
1 CloseGuard. setEnabled (true); the recommended method is to add the internal class CursorWrapperInner of ContentResolver according to the detection principle in Section 1 of this Article. Other features that need to detect resource leaks can also be used.
3. error-prone areas
There is no such thing as forgetting to call the close () method. This should also take up a large proportion. The following is an example that is not obvious.
Return in advance
Sometimes you may make this mistake carelessly and return it before calling close (). In particular, it is easier to make mistakes when the logic of the function is relatively large. In this case, you can put close () in the finally code block.Copy codeThe Code is as follows: private void method (){
Cursor cursor = query (); // assume that query () is a function that queries the database and returns the Cursor result.
If (flag = false ){//!! Return in advance
Return;
}
Cursor. close ();
}
Class member variables
Suppose there is A global member variable in the class, and the query result is obtained in method A, and the query result is obtained elsewhere, in the second query, the previous Cursor object should be closed first.Copy codeThe Code is as follows: public class TestCursor {
Private Cursor mCursor;
Private void methodA (){
MCursor = query ();
}
Private void methodB (){
//!! The previous cursor object must be closed first.
MCursor = query ();
}
}
Note: I have encountered some questions about mCursor. Why do we need to disable the same variable first? First, mCursor is a reference to a Cursor object. In methodA, mCursor points to a Cursor object 1 returned by query (). In methodB (), mCursor points to another returned Cursor object 2. You must first disable Cursor object 1 before pointing to Cursor object 2. Otherwise, the close () method is not called before finalize.
Exception Handling
An exception occurs in the code between opening and closing Cursor, causing the code to be disabled:Copy codeThe Code is as follows: try {
Cursor cursor = query ();
// Some abnormal code is omitted in the middle
Cursor. close ();
} Catch (Exception e ){
//!! Failed to run cursor. close ()
}
In this case, put close () in the finally code block:Copy codeThe Code is as follows: Cursor cursor = null;
Try {
Cursor = query ();
// Some abnormal code is omitted in the middle
} Catch (Exception e ){
// Exception
} Finally {
If (cursor! = Null)
Cursor. close ();
}
4. Summary
In finalize (), detection is feasible and can basically meet the needs. To solve the problem that the execution time of finalize () is uncertain and may not be executed, You can partially solve the problem by recording the number of Cursor that has not been closed currently. If the number of Cursor exceeds a certain number, the two methods are combined.
Are there any other detection methods? Yes. Add the log in the Cursor constructor and the close () method. After a period of operation, check where the log is not closed. The simplified code is as follows:Copy codeThe Code is as follows: import android. database. Cursor;
Import android. database. CursorWrapper;
Import android. util. Log;
Public class TestCursor extends CursorWrapper {
Private static final String TAG = "TestCursor ";
Private Throwable mTrace;
Public TestCursor (Cursor c ){
Super (c );
MTrace = new Throwable ("cusor opened here ");
Log. d (TAG, "Cursor" + this. hashCode () + "opened, stacktrace is:", mTrace );
}
@ Override
Public void close (){
MIsClosed = true;
Log. d (TAG, "Cursor" + this. hashCode () + "closed .");
}
}
Check whether the close () method has been called by the Cursor of a hashCode (). If no, the resource is leaked. This method is equally accurate and more reliable. The disadvantage is that you need to check a large number of logs, and the opening/closing locations may be far away from each other. If you do not write a small script for manual analysis, it will be painful. In addition, you must exit the APP before checking, because some Cursor is still running normally in the background.