Can someone just explain to me what is runQueryOnBackgroundThread as I already read through some sources but still not understand with it?
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint){
FilterQueryProvider filter = getFilterQueryProvider();
if (filter != null){
return filter.runQuery(constraint);
}
Uri uri = Uri.withAppendedPath(
ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(constraint.toString()));
return content.query(uri, CONTACT_PROJECTION, null, null, null);
}
A handle of my Activity in the Adapter and the runQuery call in the filter makes a call to startManagingCursor on the Activity whenever the runQuery is called. This is not ideal because a background thread is calling startManagingCursor and also there could be a lot of cursors remaining open until the Activity is destroyed.
I added the following to my Adapter which has a handle on the Activity it is used within
#Override
public void changeCursor(Cursor newCursor) {
Cursor oldCursor = getCursor();
super.changeCursor(newCursor);
if(oldCursor != null && oldCursor != newCursor) {
// adapter has already dealt with closing the cursor
activity.stopManagingCursor(oldCursor);
}
activity.startManagingCursor(newCursor);
}
This makes sure that the current cursor used by the adapter is also managed by the activity. When the cursor is closed by the adapter management by the activity is removed. The last cursor held by the adapter will be closed by the activity by way of it still be managed by the activity.
Related
Trying to refresh a ListView populated by CursorAdapter, I would have thought something like this would work:
SimpleCursorAdapter listAdapter;
#Override
protected void onRestart() {
super.onRestart();
listAdapter.swapCursor(listAdapter.getCursor());
}
In that it swaps cursor from old one to new one and the ListView gets the new data. Why doesn't this way work and what is the alternative?
Slightly more information, swapCursor() is supposed to return the old cursor but in the above it returns null.
EDIT:
I know why it doesn't work. swapCursor() checks if the new cursor and the old cursor are the same, if it is then it does nothing:
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
And changeCursor calls swapCursor so can't use that. Now to hack that into working...
Having fragment like below, in normal flow the onCreateLoader and onLoadFinished is called in pair.
But when the datasource (the database) content is changed, and since the loader is monitoring the data change the loader will issue another call to onLoadFinished() with new data filled in cursor.
But In my case it does not want to change the current cursor in use, so don't want the loader deliver the updated cursor vis another onLoadFinished call, or disable the loader's monitoring part.
Is there a way to do it?
AFragment extends Fragment implements LoaderCallbacks<Cursor> {
protected void startSupportLoaderManager() {
getActivity().getSupportLoaderManager()
.initLoader(LOADER_ID, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return createLoader(getActivity(), id, args, null);
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
if (mAdapter != null) {
mAdapter.resetCursor();
}
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
...
}
}
EDIT:
Kinda of knowing this may work, but feel still missing some dot. Here is what the thought:
In the implementation of ContentProvider, for insert(), update(), delete() we do
getContext().getContentResolver().notifyChange(uri, null);
And in the CursorLoader it did cursor.registerContentObserver(mObserver); in
public Cursor loadInBackground() {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder);
if (cursor != null) {
// Ensure the cursor window is filled
cursor.getCount();
cursor.registerContentObserver(mObserver);
}
return cursor;
}
So it in some way is monitoring the Uri data source change, same as we do on cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri);
So if we could provide a Uri, which could used for insert/update/delete, but is different than the Uri we give to the Loader, that would still do the inert/update/delet operation but the loader will not be notified because of the Uri is different.
So basically, the loader will use different Uri than the Uri the other data operation using.
Not sure If understanding how the Loader's monitoring with the content Uri is correct. Maybe there is better way of doing it?
Any suggestion is appreciated.
If you want to stop the loader, you can use:
getLoaderManager().destroyLoader(LOADER_ID);
I'm using a SimpleCursorAdapter to display results in a ListView but since I've got to query my database lots of times during a search (using the SearchView widget) it worries me that the cursor might be left opened.
This is how I query my database and show the results in a ListView:
class SearchCustomers extends AsyncTask<String,Void,Cursor>{
#Override
protected Cursor doInBackground(String... params) {
//get the query
String query=params[0].toLowerCase(Locale.getDefault());
Cursor cursor=mDB.searchCustomersByName((query != null ? query : "####"));
return cursor;
}
#Override
protected void onPostExecute(Cursor result) {
if (result != null) {
String[] from = new String[] { QuickOrderDB.ID,
QuickOrderDB.NAME,
QuickOrderDB.ADDRESS,
QuickOrderDB.PHONE_NUMBER };
int[] to = new int[] { R.id.customerIDTextView,
R.id.customerNameTextView,R.id.customerAddressTextView ,
R.id.customerPhoneTextView };
SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter(SearchCustomersActivity.this,
R.layout.results_customer_item, result, from, to);
mResultsListView.setAdapter(cursorAdapter);
}
}
}
I have tried many things to close the cursor, but even If I close it after mResultsListView.setAdapter(cursorAdapter); the result is always the same: an empty ListView.
I've already seen a couple of questions in which it is mentioned that the cursor will be closed automatically, but I want to make sure this is true.
Is there any official documentation about this? Does the SimpleCursorAdapter really close the cursor automatically??
Thanks in advance.
You need to close your cursor once you are done with it. Closing it after setAdapter() call would prevent the adapter from accessing the data. Hence a better place to close the cursor would be during current activities tear down life cycle stages such as onPause() or onStop(). (onDestroy() should not be used as Android run-time does not guarantee calling it. I think on latest version onStop() is guaranteed)
I don't think SimpleCursorAdapter adapter automatically closes the cursor automatically. The official document mentions that changeCursor() automatically closes the old cursor, so another option could be to change your cursor after search.
http://developer.android.com/reference/android/widget/CursorAdapter.html#changeCursor(android.database.Cursor)
It's better if you get the Cursor using a CursorLoader instead of an AsyncTask.
The Loaders are synched to the Activity/Fragment lifecycle via the LoaderManager, and the system will close the Cursor provided by the CursorLoader automatically for you when it's needed.
You should close the cursor in your fragment or activity's onPause() callback. After the activity is paused it's possible that older Android systems will delete the app to free memory.
This implies that you need to reestablish the cursor in the corresponding onResume() callback.
Don't create a variable for the cursor, just add the db query directly into the constructor as the argument c, db.query() or a method that holds the desired query), this seems to work.
SimpleCursorAdapter (Context context,
int layout,
Cursor c,
String[] from,
int[] to,
int flags)
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
}
}
I can use a managedQuery like this:
Activity a = (Activity) context;
cursor = a.managedQuery(uri, null, null, null, null);
And once I do, I have a cursor that I can step through however I want.
However, using a CursorLoader, when a new CursorLoader is created the onCreateLoader call back method is called. The onCreateLoader call back returns a CursorLoader. How do I get a reference to the cursor so I can step though it, as in managedCursor.
I missing the boat here, appreciate any direction.
You need to also implement onLoadFinished, this method gives you the Cursor when the asynchronous load has finished
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
myadapter.swapCursor(cursor);
}