ANR Crash When SQLite Connection Closed By DBManager.setTalkProgress()
Introduction
In this article, we will investigate an App Not Responding (ANR) crash that occurs when the SQLite connection is closed by DBManager.setTalkProgress()
. This crash is triggered when the user is actively playing a talk and scrolling through a long list of talks or teachers.
Reproduction Steps
To reproduce this issue, follow these steps:
- Launch the app and start playing a talk.
- Simultaneously, scroll through a long list of talks or teachers.
- The app will crash with an ANR error.
ADB Logcat Output
The ADB logcat output shows that the crash is caused by an IllegalStateException
when trying to perform an operation on a closed SQLite connection pool.
03-10 23:02:51.965 17310 17310 E AndroidRuntime: FATAL EXCEPTION: main
03-10 23:02:51.965 17310 17310 E AndroidRuntime: Process: org.dharmaseed.android, PID: 17310
03-10 23:02:51.965 17310 17310 E AndroidRuntime: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:1108)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:721)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:404)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:931)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:871)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:153)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:123)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:269)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at androidx.cursoradapter.widget.CursorAdapter.getItemId(CursorAdapter.java:242)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.AbsListView$RecycleBin.retrieveFromScrap(AbsListView.java:7657)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.AbsListView$RecycleBin.getScrapView(AbsListView.java:7368)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.AbsListView.obtainView(AbsListView.java:2474)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.ListView.makeAndAddView(ListView.java:2065)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.ListView.fillDown(ListView.java:791)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.ListView.fillGap(ListView.java:754)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.AbsListView.trackMotionScroll(AbsListView.java:5617)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.ListView.trackMotionScroll(ListView.java:1982)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.widget.AbsListView$FlingRunnable.run(AbsListView.java:5155)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1415)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1424)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:1024)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:949)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1398)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:991)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:232)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.os.Looper.loop(Looper.java:317)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8787)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:871)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: Caused by: java.lang.Exception: SQLiteConnectionPool.close()
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteConnectionPool.dispose(SQLiteConnectionPool.java:272)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteConnectionPool.close(SQLiteConnectionPool.java:252)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteDatabase.dispose(SQLiteDatabase.java:559)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteDatabase.onAllReferencesReleased(SQLiteDatabase.java:536)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteClosable.releaseReference(SQLiteClosable.java:92)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at android.database.sqlite.SQLiteClosable.close(SQLiteClosable.java:124)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at org.dharmaseed.android.DBManager.setTalkProgress(DBManager.java:380)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at org.dharmaseed.android.PlaybackService.updateTalkProgress(PlaybackService.java:104)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at org.dharmaseed.android.PlaybackService.access$100(PlaybackService.java:41)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: at org.dharmaseed.android.PlaybackService$1.run(PlaybackService.java:88)
03-10 23:02:51.965 17310 17310 E AndroidRuntime: ... 8 more
Analysis
The crash is caused by the DBManager.setTalkProgress()
method closing the SQLite connection pool while the ListView
is still trying to render data from the same connection pool. This is evident from the stacktrace, which shows that the SQLiteConnectionPool
is being closed in DBManager.setTalkProgress()
(line 380), and then the ListView
is trying to acquire a connection from the closed pool.
Q: What is the cause of the ANR crash?
A: The ANR crash is caused by the DBManager.setTalkProgress()
method closing the SQLite connection pool while the ListView
is still trying to render data from the same connection pool.
Q: Why is the SQLite connection pool being closed?
A: The SQLite connection pool is being closed in the DBManager.setTalkProgress()
method. This is likely done to release system resources and prevent memory leaks.
Q: Why is the ListView
trying to render data from the closed connection pool?
A: The ListView
is trying to render data from the closed connection pool because it is still holding a reference to the connection pool. This is likely due to the CursorAdapter
used by the ListView
still holding a reference to the cursor, which in turn holds a reference to the connection pool.
Q: How can we fix this issue?
A: To fix this issue, we need to ensure that the ListView
releases its reference to the connection pool before the DBManager.setTalkProgress()
method closes it. We can do this by calling CursorAdapter.changeCursor()
to release the cursor reference, and then closing the connection pool.
Q: What are some best practices to avoid this issue in the future?
A: To avoid this issue in the future, follow these best practices:
- Always close the SQLite connection pool when it is no longer needed.
- Ensure that all references to the connection pool are released before closing it.
- Use a
CursorLoader
to load data from the database, which will automatically release the cursor reference when the data is loaded. - Use a
RecyclerView
instead of aListView
, which will automatically release the cursor reference when the data is loaded.
Q: How can we debug this issue?
A: To debug this issue, follow these steps:
- Use the ADB logcat to capture the stacktrace of the ANR crash.
- Analyze the stacktrace to identify the cause of the crash.
- Use a debugger to step through the code and identify where the connection pool is being closed.
- Use a tool like Android Studio's Memory Profiler to identify memory leaks and ensure that all references to the connection pool are released.
Q: What are some common mistakes that can lead to this issue?
A: Some common mistakes that can lead to this issue include:
- Closing the SQLite connection pool while it is still being used by another component.
- Failing to release references to the connection pool before closing it.
- Using a
CursorAdapter
with a cursor that holds a reference to the connection pool. - Failing to use a
CursorLoader
to load data from the database.
By following these best practices and avoiding common mistakes, you can help prevent this issue from occurring in the future.