How does a cursor used by a RemoteViewService get deactivated - android

I get ACRA exception reports from some users that the cursor which supplies data to my appwidget (RemoteViewService) is deactivated/closed. It never happens to me in person, but it happens enough where it's a bit of an issue.
Here's the code to my RemoteViewService:
public static class ListItemService extends RemoteViewsService {
public RemoteViewsFactory onGetViewFactory(final Intent intent) {
return new RemoteViewsFactory() {
private MyCursor cursor;
public void onCreate() {
// Nothing
}
public synchronized void onDestroy() {
if (this.cursor != null)
this.cursor.close();
}
public synchronized RemoteViews getViewAt(int position) {
// Here I read from the cursor and it crashes with
// the stack trace below
}
public int getCount() {
return ((this.cursor != null) ? this.cursor.getCount() : 0);
}
public int getViewTypeCount() {
return 1;
}
public boolean hasStableIds() {
return true;
}
public long getItemId(int position) {
return position;
}
public RemoteViews getLoadingView() {
return null;
}
public synchronized void onDataSetChanged()
{
if (this.cursor != null)
this.cursor.close();
this.cursor = getApplicationCntext().getContentResolver().query(myUri, null, null, null, null);
}
};
The stack trace varies from platform version to platform version. For example, I get the following on 4.0.3:
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
On 2.3, I get a:
java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery
I cannot, for the life of me, figure out who or what closes the cursor on me, other than from onDestroy() and onDataSetChanged(). Some users had reported that they weren't actively working with app when the crash happened.
I suspected maybe multiple calls to the ContentProvider return the same cursor and when I display my UI which uses the same query, they step on each other. But this doesn't appear to be the case as the cursor objects are different.
Any ideas?

Looks to me like a sync issue between multiple threads, one closing the previous cursor and one immediately accessing it thereafter.
You may want to consider closing the cursor right when you first can, such as a onPause event.
Also - you can add a safety precaution as checking cursor.isClosed() before you access it again. You can also add some synchronisation to your code.
A helper method that fetches the new cursor with a local var and only once it's done, replaces the previous one and closes it may be a quicker solution in the meantime.
From AOSP doc;
This interface provides random read-write access to the result set
returned by a database query. Cursor implementations are not required
to be synchronized so code using a Cursor from multiple threads should
perform its own synchronization when using the Cursor.
From Content provider basics,
The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query's projection for the rows that match the query's selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursor implementations automatically update the object when the provider's data changes, or trigger methods in an observer object when the Cursor changes, or both.

Try to add this.cursor = null on onDestory() method
public synchronized void onDestroy() {
if (this.cursor != null){
this.cursor.close();
this.cursor = null
}
}

Related

Cursor#getCount is used to ensure content window is filled?

Below is a code snippet from an Android tutorial book I was following. loadInBackground gets a cursor and then does cursor.getCount() to "ensure that the content window is filled". What does this mean? The docs on getCount just say "returns number of rows in the cursor". I've Googled for this "ensure that the content window is filled" and there are quite a few snippets that do this, all with the same comment, but no explanation of why this is needed/how it works.
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.database.Cursor;
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor cursor;
public SQLiteCursorLoader(Context context) {
super(context);
}
protected abstract Cursor loadCursor();
#Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
cursor.getCount(); // ensure that the content window is filled
}
return cursor;
}
}
As you know, Database returns a Cursor after a query. However, Cursor is only effectively filled with data when you try to read some information like: cursor.getCount() or cursor.moveToFirst() etc...
This is more evident during large queries.
For example, imagine that query below would return thousand of results:
Cursor cursor = db.rawQuery("select * from TABLE", null);
That statement however, does not take too much time to run...
However, when you finally call cursor.getCount() or cursor.moveToFirst(), for the first time, you may see some "lag" since the cursor is being effectively being filled with the data from database.
If you do that in Main UI Thread, you app may freeze for some seconds. Specially on low tier devices.
So, by calling that method, I believe that author is trying to ensure that data was fully loaded during loadInBackground(). This way, he ensure that data is loaded in Background an not in any other future method. This way, any future call to getCount() or moveToFirst() will be executed very quickly since the data was already loaded.
Anyway, it is not mandatory..

Proper way to handle Cursor objects

When you call .close() on a Cursor Object, does it mean that for the rest of the Activity's duration it cannot be used? The following is a method within my Manager Object:
Cursor cursor = null;
try {
SQLiteDatabase db = openDb();
cursor = db.query("table", null, "id=?", new String[] { id }, null, null, null);
cursor.moveToFirst();
long dateTime = cursor.getLong(1);
cursor.close();
return dateTime ;
} catch (CursorIndexOutOfBoundsException e) {
return -1;
} finally {
if (cursor != null) {
cursor.close();
}
closeDb();
}
This is the method that's throwing me an IllegalStateException. However, there's a slight twist: it only throws an error the second time it is called. Tracing the stacktrace, I find that the line causing me trouble is the following:
Cursor cursor = db.query("table", null, "id=?", new String[] { id }, null, null, null);
Just to clear things up a bit, this method can be called several times within the Activity's lifetime through clicking of a particular ListView item. The openDb() and closeDb() methods are as follows:
public SQLiteDatabase openDb() {
if (mDbHelper == null) {
mDbHelper = new DatabaseHelper(mContext);
}
return mDbHelper.getWritableDatabase();
}
public void closeDb() {
mDbHelper.close();
}
And these are stored in the superclass of my Manager object. mDbHelper is a static Object.
Being fairly new to Android programming, I'm wondering why this would throw me an exception. The only logical explanation I can think of is that Cursor Objects are actually re-used, and they should not be closed for the duration of an activity. Am I right? And if I am, when do you actually close the Cursor?
---EDIT---
Having tinkered around with the code a bit, I seem to be getting the exception being thrown on a much more irregular basis. For some odd reason, it seems to happen randomly; I can click on eight multiple ListView items with no issues, and suddenly bam! The ninth causes the application to crash.
Because clicking on a ListView also invokes a method which updates that very same table (which up till now has caused me no problems thus far), I think it's only relevant that I include that as well:
try {
SQLiteDatabase db = openDb();
ContentValues cv = new ContentValues();
cv.put("id", id);
cv.put("dateTime", dateTime);
long affected = db.replace("table", null, cv);
return affected;
} finally {
closeDb();
}
As you can see, no rocket science is involved here. However, this method has now started to throw similar Exceptions, happening on the line:
long affected = db.replace("table", null, cv);
I'm starting to suspect that it's a click-too-fast problem, and the SQLite connections are not given enough time to close. Because there is no pattern to the crashes that I can discern; sometimes it crashes on the third try, sometimes on the eighth, sometimes it even seems to work fine till well past the tenth.
Could that be possible?
As the docs say after you have called close() your Cursor becomes forever invalid.
Also, there's no need to call close 2 times in your function. It's enough to call it in the finally block only
Because you call the close() method on the static object, it may not necessarily "nullify" the static object. So when you check if mDbHelper is null in the openDb() method the second time, it will pass this condition, and therefore the method will unintentionally return a closed database. When you try and query this closed database, it will therefore throw the illegalstateexception.
Try:
public SQLiteDatabase closeDb() {
mDbHelper.close()
mDbHelper = null;
}
I hope I have helped.

Android Combine ImageDownload with CursorLoader

I am using a very simple CursorLoader with an ImageDownloader. The ImageDownloader is running and everything but the CursorLoader finishes, and THEN the ImageDownloader begins its job of downloading the images, but the GridView is not updating with the downloaded images..
Within my ListFragment I have the following onActivityCreated method:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
cursorAdapter = new OURSVPImageAdapter(getActivity(), null);
// set the adapter on the gridview
mGridView.setAdapter(cursorAdapter);
// load the data
getActivity().getSupportLoaderManager().initLoader(2, null, this);
}
My CursorLoader is as follows:
public static final class PhotoCursorLoader extends SimpleCursorLoader {
Context mContext;
public PhotoCursorLoader(Context context) {
super(context);
Log.d("PhotoCursorLoader", "Constructor");
mContext = context;
}
#Override
public Cursor loadInBackground() {
Log.d("PhotoCursorLoader", "loadInBackground");
PhotosDataSource datasource = new PhotosDataSource(mContext);
# STEP 1
return datasource.getAllPhotos(((EventActivity) mContext).getEventId());
}
}
The line labeled # STEP 1 that retrieves all of the photos is just a method that retrieves a Cursor, as seen here:
public Cursor getAllPhotos(long event_id) {
Log.d(TAG, "getAllPhotos");
Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_PHOTOS, DatabaseConstants.KEY_EVENT_ID + "=" + event_id,
null, null, null, null, null);
return mCursor;
}
So, this being the CursorLoader for the ListFragment, it assumes its complete when that Cursor is returned, which is correct. From my understanding the setAdapter() method is what would actually trigger the getView method.
The Trouble I'm having is that Everything seems to be running fine, my log output is outputting the urls for the images correctly, breakpoints all showing legit data, the issue though, is that my grid view never gets updated with the images that the ImageDownloader retrieves.
EDIT
This is the SimpleCursorLoader I'm using: https://gist.github.com/1217628
Once again, the problem is most likely that you are managing your Fragment's loaders from the parent Activity. The Activity and Fragment lifecycles do not sync with each other (at least not in a predictable manner), so your attempts at debugging the issue aren't actually getting you anywhere. The easiest way to guarantee predictable behavior is to have each separate component make use of its own LoaderManager (as opposed to having your Fragments access getActivity().getSupportLoaderManager()).
As with another of my questions.. the refresh shouldn't be forced, and when using a provider you can have the provider notify the contentresovler of updates. I have a method that inserts new photos, and that method uses the data provider. The data provider needed to
getContext().getContentResolver().notifyChange(uri, null);
to actually update the Cursor.

Android ContentProvider calls bursts of setNotificationUri() to CursorAdapter when many rows are inserted with a batch operation

I have a custom ContentProvider which manages the access to a SQLite database. To load the content of a database table in a ListFragment, I use the LoaderManager with a CursorLoader and a CursorAdapter:
public class MyListFragment extends ListFragment implements LoaderCallbacks<Cursor> {
// ...
CursorAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new CursorAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), CONTENT_URI, PROJECTION, null, null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
}
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
The SQLite database gets updated by a background task which fetches a number of items from a web service and inserts these items into the database through ContentProvider batch operations (ContentResolver#applyBatch()).
Even if this is a batch operation, ContentProvider#insert() gets called for each row is inserted into the database and, in the current implementation, the ContentProvider calls setNotificationUri() for each insert command.
The result is that the CursorAdapter receives bursts of notifications, resulting in the UI being updated too often with consequent annoying flickering effect.
Ideally, when a batch operation is in progress, there should be a way to notify ContentObserver only at the end of any batch operation and not with each insert command.
Does anybody know if this is possible? Please note I can change the ContentProvider implementation and override any of its methods.
I found a simpler solution that I discovered from Google's I/O app. You just have to override the applyBatch method in your ContentProvider and perform all the operations in a transaction. Notifications are not sent until the transaction commits, which results in minimizing the number of ContentProvider change-notifications that are sent out:
#Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
final SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
for (int i = 0; i < numOperations; i++) {
results[i] = operations.get(i).apply(this, results, i);
}
db.setTransactionSuccessful();
return results;
} finally {
db.endTransaction();
}
}
To address this exact problem, I overrode applyBatch and set a flag which blocked other methods from sending notifications.
volatile boolean applyingBatch=false;
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
applyingBatch=true;
ContentProviderResult[] result;
try {
result = super.applyBatch(operations);
} catch (OperationApplicationException e) {
throw e;
}
applyingBatch=false;
synchronized (delayedNotifications) {
for (Uri uri : delayedNotifications) {
getContext().getContentResolver().notifyChange(uri, null);
}
}
return result;
}
I exposed a method to "store" notifications to be sent when the batch was complete:
protected void sendNotification(Uri uri) {
if (applyingBatch) {
if (delayedNotifications==null) {
delayedNotifications=new ArrayList<Uri>();
}
synchronized (delayedNotifications) {
if (!delayedNotifications.contains(uri)) {
delayedNotifications.add(uri);
}
}
} else {
getContext().getContentResolver().notifyChange(uri, null);
}
}
And any methods that send notifications employ sendNotification, rather than directly firing a notification.
There may well be better ways of doing this - it certainly seems as though they're ought to be - but that's what I did.
In a comment to the original answer, Jens directed us towards SQLiteContentProvider in the AOSP. One reason why this isn't (yet?) in the SDK may be that the AOSP seems to contain multiple variations of this code.
For example com.android.browser.provider.SQLiteContentProvider seems to be a slightly more complete solution, incorporating the "delayed notifications" principle proposed by Phillip Fitzsimmons while keeping the provider thread-safe by using a ThreadLocal for the batch-flag and synchronizing access to the delayed notification set.
Yet even though access to the set of URI's to be notified of change is synchronized, I can still imagine that race conditions may occur. For example if a long operation posts some notifications, then gets overtaken by a smaller batch operation, which fires the notifications and clears the set before the first operation commits.
Still, the above version seems to be the best bet as a reference when implementing your own provider.

Does this if/elseif make my Cursor query the db twice?

Say I've got a Cursor just returned from a database call:
Cursor myCursor = db.rawQuery(someQuery, null);
According to the docs, the Cursor isn't actually populated before any calls are made to it, like getCount() for example. So my question is, does the following code actually query my database twice?
if(myCursor.getCount() > 1)
{
// Do something
}
else if(myCursor.getCount() == 1)
{
// Do something else
}
Or does Android cache the Cursor object after the first 'if' statement, making the 'else if' statement access the cached object instead?
No, it doesn't. The cursor object actually contains it's own count. You can see the source code for the SQLiteCursor object here.Take a look at the getCount() method:
#Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
}
It will only check the first time. mCount is only updated when the Cursor is requeried (see QueryThread.run() on the same page).

Categories

Resources