Fix issue #3381489: IllegalStateException: attempt to re-open...

...an already-closed object: android.database.sqlite.SQLiteQuery

It turns out there is a state we are missing -- the loader is
still needed, but in the inactive list.  In this case the loader
needs to continue holding on to its current data, and not deliver
any new data (which would result in it releasing its old data).

This introduces the new state to Loader, and uses it in
AsyncTaskLoader so all subclasses of that should get the new
correct behavior.

A further improvement would be to unregister CursorLoader's
content listener when going in to this state, but that can
wait for later.

Change-Id: I6d30173b94f8e30b5be31d018accd328cc3388ec
This commit is contained in:
Dianne Hackborn
2011-01-30 16:55:55 -08:00
parent fba54f620f
commit 260c3c77d9
5 changed files with 112 additions and 35 deletions

View File

@@ -43763,19 +43763,6 @@
<parameter name="data" type="D">
</parameter>
</method>
<method name="onCancelled"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="deprecated"
visibility="public"
>
<parameter name="data" type="D">
</parameter>
</method>
<method name="onLoadInBackground"
return="D"
abstract="false"
@@ -55609,6 +55596,17 @@
<parameter name="context" type="android.content.Context">
</parameter>
</constructor>
<method name="abandon"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="dataToString"
return="java.lang.String"
abstract="false"
@@ -55687,6 +55685,17 @@
visibility="public"
>
</method>
<method name="isAbandoned"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="isReset"
return="boolean"
abstract="false"
@@ -55709,6 +55718,17 @@
visibility="public"
>
</method>
<method name="onAbandon"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
</method>
<method name="onContentChanged"
return="void"
abstract="false"

View File

@@ -43763,19 +43763,6 @@
<parameter name="data" type="D">
</parameter>
</method>
<method name="onCancelled"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="deprecated"
visibility="public"
>
<parameter name="data" type="D">
</parameter>
</method>
<method name="onLoadInBackground"
return="D"
abstract="false"
@@ -55609,6 +55596,17 @@
<parameter name="context" type="android.content.Context">
</parameter>
</constructor>
<method name="abandon"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="dataToString"
return="java.lang.String"
abstract="false"
@@ -55687,6 +55685,17 @@
visibility="public"
>
</method>
<method name="isAbandoned"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="isReset"
return="boolean"
abstract="false"
@@ -55709,6 +55718,17 @@
visibility="public"
>
</method>
<method name="onAbandon"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
</method>
<method name="onContentChanged"
return="void"
abstract="false"

View File

@@ -587,6 +587,7 @@ class LoaderManagerImpl extends LoaderManager {
if (DEBUG) Log.v(TAG, " Removing last inactive loader: " + info);
inactive.mDeliveredData = false;
inactive.destroy();
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
} else {
// We already have an inactive loader for this ID that we are
@@ -617,6 +618,7 @@ class LoaderManagerImpl extends LoaderManager {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
if (DEBUG) Log.v(TAG, " Making last loader inactive: " + info);
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}

View File

@@ -169,11 +169,6 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
* to properly dispose of the result.
*/
public void onCanceled(D data) {
onCancelled(data);
}
@Deprecated
public void onCancelled(D data) {
}
void executePendingTask() {
@@ -214,10 +209,15 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
} else {
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Slog.v(TAG, "Delivering result");
deliverResult(data);
if (isAbandoned()) {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Slog.v(TAG, "Delivering result");
deliverResult(data);
}
}
}

View File

@@ -45,6 +45,7 @@ public class Loader<D> {
OnLoadCompleteListener<D> mListener;
Context mContext;
boolean mStarted = false;
boolean mAbandoned = false;
boolean mReset = true;
boolean mContentChanged = false;
@@ -150,6 +151,15 @@ public class Loader<D> {
return mStarted;
}
/**
* Return whether this loader has been abandoned. In this state, the
* loader <em>must not</em> report any new data, and <em>must</em> keep
* its last reported data valid until it is finally reset.
*/
public boolean isAbandoned() {
return mAbandoned;
}
/**
* Return whether this load has been reset. That is, either the loader
* has not yet been started for the first time, or its {@link #reset()}
@@ -177,6 +187,7 @@ public class Loader<D> {
public final void startLoading() {
mStarted = true;
mReset = false;
mAbandoned = false;
onStartLoading();
}
@@ -235,6 +246,28 @@ public class Loader<D> {
protected void onStopLoading() {
}
/**
* Tell the Loader that it is being abandoned. This is called prior
* to {@link #reset} to have it retain its current data but not report
* any new data.
*/
public void abandon() {
mAbandoned = true;
onAbandon();
}
/**
* Subclasses implement this to take care of being abandoned. This is
* an optional intermediate state prior to {@link #onReset()} -- it means that
* the client is no longer interested in any new data from the loader,
* so the loader must not report any further updates. However, the
* loader <em>must</em> keep its last reported data valid until the final
* {@link #onReset()} happens. You can retrieve the current abandoned
* state with {@link #isAbandoned}.
*/
protected void onAbandon() {
}
/**
* Resets the state of the Loader. The Loader should at this point free
* all of its resources, since it may never be called again; however, its
@@ -251,6 +284,7 @@ public class Loader<D> {
onReset();
mReset = true;
mStarted = false;
mAbandoned = false;
mContentChanged = false;
}
@@ -327,6 +361,7 @@ public class Loader<D> {
writer.print(" mListener="); writer.println(mListener);
writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
writer.print(" mContentChanged="); writer.print(mContentChanged);
writer.print(" mAbandoned="); writer.print(mAbandoned);
writer.print(" mReset="); writer.println(mReset);
}
}