summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dianne Hackborn <hackbod@google.com> 2010-12-15 14:57:25 -0800
committer Dianne Hackborn <hackbod@google.com> 2010-12-16 20:09:13 -0800
commitc91893511dc1b9e634648406c9ae61b15476e65d (patch)
tree3ad578a43734d2ed63403a8b9076c2c5c6d07dbe
parent60e41fa4456ce6bc37a33b1e4b81a56e9411199b (diff)
Fix issue #3272082: Contacts: when going back from edit view,
list UI is not ready yet This involves some reworking of Loaders. Loaders, in particular CursorLoader, are now expected to retain their current data after being stopped. This allows applications to keep that data across onStop() -> onStart(), so when the user returns to the app it doesn't have to wait for the data to reload and thus cause flicker. This includes various API changes to better reflect the new semantics, plus a new LoaderCallbacks method to tell the application when it is actually time to stop their use of a loader's data. Note this is somewhat half-done, to help checking in the extensive application changes that are required without causing build breakage. Change-Id: Ib4b3bf8185a6da46e7f06ca125521d65e2e380a1
-rw-r--r--api/current.xml226
-rw-r--r--core/java/android/app/LoaderManager.java123
-rw-r--r--core/java/android/app/LoaderManagingFragment.java207
-rw-r--r--core/java/android/content/CursorLoader.java25
-rw-r--r--core/java/android/content/Loader.java24
-rw-r--r--core/java/android/view/Window.java10
-rw-r--r--core/java/android/widget/CursorAdapter.java93
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java23
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java25
-rw-r--r--test-runner/src/android/test/LoaderTestCase.java2
10 files changed, 333 insertions, 425 deletions
diff --git a/api/current.xml b/api/current.xml
index 6ff0ee3904a5..86023fbcb606 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -32206,6 +32206,19 @@
visibility="public"
>
</constructor>
+<method name="destroyLoader"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
<method name="dump"
return="void"
abstract="true"
@@ -32274,12 +32287,12 @@
</method>
<method name="stopLoader"
return="void"
- abstract="true"
+ abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="id" type="int">
@@ -32323,54 +32336,7 @@
<parameter name="data" type="D">
</parameter>
</method>
-</interface>
-<class name="LoaderManagingFragment"
- extends="android.app.Fragment"
- abstract="true"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<implements name="android.content.Loader.OnLoadCompleteListener">
-</implements>
-<constructor name="LoaderManagingFragment"
- type="android.app.LoaderManagingFragment"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</constructor>
-<method name="getLoader"
- return="android.content.Loader&lt;D&gt;"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="id" type="int">
-</parameter>
-</method>
-<method name="onCreateLoader"
- return="android.content.Loader&lt;D&gt;"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="id" type="int">
-</parameter>
-<parameter name="args" type="android.os.Bundle">
-</parameter>
-</method>
-<method name="onInitializeLoaders"
+<method name="onLoaderReset"
return="void"
abstract="true"
native="false"
@@ -32378,68 +32344,12 @@
static="false"
final="false"
deprecated="not deprecated"
- visibility="protected"
->
-</method>
-<method name="onLoadComplete"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
visibility="public"
>
<parameter name="loader" type="android.content.Loader&lt;D&gt;">
</parameter>
-<parameter name="data" type="D">
-</parameter>
-</method>
-<method name="onLoadFinished"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="loader" type="android.content.Loader&lt;D&gt;">
-</parameter>
-<parameter name="data" type="D">
-</parameter>
</method>
-<method name="startLoading"
- return="android.content.Loader&lt;D&gt;"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="id" type="int">
-</parameter>
-<parameter name="args" type="android.os.Bundle">
-</parameter>
-</method>
-<method name="stopLoading"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="id" type="int">
-</parameter>
-</method>
-</class>
+</interface>
<class name="LocalActivityManager"
extends="java.lang.Object"
abstract="false"
@@ -49433,17 +49343,6 @@
<parameter name="cursor" type="android.database.Cursor">
</parameter>
</method>
-<method name="destroy"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="getProjection"
return="java.lang.String[]"
abstract="false"
@@ -54750,12 +54649,12 @@
</method>
<method name="destroy"
return="void"
- abstract="true"
+ abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -54818,6 +54717,17 @@
<parameter name="listener" type="android.content.Loader.OnLoadCompleteListener&lt;D&gt;">
</parameter>
</method>
+<method name="reset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="startLoading"
return="void"
abstract="true"
@@ -234083,7 +233993,7 @@
type="android.widget.CursorAdapter"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="context" type="android.content.Context">
@@ -234256,7 +234166,7 @@
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="protected"
>
<parameter name="context" type="android.content.Context">
@@ -234266,23 +234176,6 @@
<parameter name="autoRequery" type="boolean">
</parameter>
</method>
-<method name="init"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="c" type="android.database.Cursor">
-</parameter>
-<parameter name="flags" type="int">
-</parameter>
-</method>
<method name="newDropDownView"
return="android.view.View"
abstract="false"
@@ -234354,6 +234247,19 @@
<parameter name="filterQueryProvider" type="android.widget.FilterQueryProvider">
</parameter>
</method>
+<method name="swapCursor"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newCursor" type="android.database.Cursor">
+</parameter>
+</method>
<field name="FLAG_AUTO_REQUERY"
type="int"
transient="false"
@@ -234361,7 +234267,7 @@
value="1"
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -243872,7 +243778,7 @@
type="android.widget.ResourceCursorAdapter"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="context" type="android.content.Context">
@@ -245304,6 +245210,24 @@
type="android.widget.SimpleCursorAdapter"
static="false"
final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="layout" type="int">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="from" type="java.lang.String[]">
+</parameter>
+<parameter name="to" type="int[]">
+</parameter>
+</constructor>
+<constructor name="SimpleCursorAdapter"
+ type="android.widget.SimpleCursorAdapter"
+ static="false"
+ final="false"
deprecated="not deprecated"
visibility="public"
>
@@ -245317,6 +245241,8 @@
</parameter>
<parameter name="to" type="int[]">
</parameter>
+<parameter name="flags" type="int">
+</parameter>
</constructor>
<method name="bindView"
return="void"
@@ -251263,7 +251189,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
</parameter>
</method>
</interface>
@@ -335167,7 +335093,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -335235,7 +335161,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -335259,7 +335185,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -340902,7 +340828,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -340913,7 +340839,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -340924,7 +340850,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -343294,7 +343220,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -358368,7 +358294,7 @@
native="false"
synchronized="false"
static="true"
- final="false"
+ final="true"
deprecated="not deprecated"
visibility="public"
>
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 0ab987ad2e2c..e5db00ddc7ec 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -26,7 +26,19 @@ import java.io.PrintWriter;
/**
* Interface associated with an {@link Activity} or {@link Fragment} for managing
- * one or more {@link android.content.Loader} instances associated with it.
+ * one or more {@link android.content.Loader} instances associated with it. This
+ * helps an application manage longer-running operations in conjunction with the
+ * Activity or Fragment lifecycle; the most common use of this is with a
+ * {@link android.content.CursorLoader}, however applications are free to write
+ * their own loaders for loading other types of data.
+ *
+ * <p>As an example, here is the full implementation of a {@link Fragment}
+ * that displays a {@link android.widget.ListView} containing the results of
+ * a query against the contacts content provider. It uses a
+ * {@link android.content.CursorLoader} to manage the query on the provider.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
+ * fragment_cursor}
*/
public abstract class LoaderManager {
/**
@@ -49,10 +61,48 @@ public abstract class LoaderManager {
* activity's state is saved. See {@link FragmentManager#openTransaction()
* FragmentManager.openTransaction()} for further discussion on this.
*
+ * <p>This function is guaranteed to be called prior to the release of
+ * the last data that was supplied for this Loader. At this point
+ * you should remove all use of the old data (since it will be released
+ * soon), but should not do your own release of the data since its Loader
+ * owns it and will take care of that. The Loader will take care of
+ * management of its data so you don't have to. In particular:
+ *
+ * <ul>
+ * <li> <p>The Loader will monitor for changes to the data, and report
+ * them to you through new calls here. You should not monitor the
+ * data yourself. For example, if the data is a {@link android.database.Cursor}
+ * and you place it in a {@link android.widget.CursorAdapter}, use
+ * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
+ * android.database.Cursor, int)} constructor <em>without</em> passing
+ * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
+ * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
+ * (that is, use 0 for the flags argument). This prevents the CursorAdapter
+ * from doing its own observing of the Cursor, which is not needed since
+ * when a change happens you will get a new Cursor throw another call
+ * here.
+ * <li> The Loader will release the data once it knows the application
+ * is no longer using it. For example, if the data is
+ * a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
+ * you should not call close() on it yourself. If the Cursor is being placed in a
+ * {@link android.widget.CursorAdapter}, you should use the
+ * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
+ * method so that the old Cursor is not closed.
+ * </ul>
+ *
* @param loader The Loader that has finished.
* @param data The data generated by the Loader.
*/
public void onLoadFinished(Loader<D> loader, D data);
+
+ /**
+ * Called when a previously created loader is being reset, and thus
+ * making its data unavailable. The application should at this point
+ * remove any references it has to the Loader's data.
+ *
+ * @param loader The Loader that is being reset.
+ */
+ public void onLoaderReset(Loader<D> loader);
}
/**
@@ -65,20 +115,33 @@ public abstract class LoaderManager {
* will be called as the loader state changes. If at the point of call
* the caller is in its started state, and the requested loader
* already exists and has generated its data, then
- * callback. {@link LoaderCallbacks#onLoadFinished} will
+ * callback {@link LoaderCallbacks#onLoadFinished} will
* be called immediately (inside of this function), so you must be prepared
* for this to happen.
+ *
+ * @param id A unique identifier for this loader. Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param args Optional arguments to supply to the loader at construction.
+ * @param callback Interface the LoaderManager will call to report about
+ * changes in the state of the loader. Required.
*/
public abstract <D> Loader<D> initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
/**
- * Creates a new loader in this manager, registers the callbacks to it,
+ * Starts a new or restarts an existing {@link android.content.Loader} in
+ * this manager, registers the callbacks to it,
* and (if the activity/fragment is currently started) starts loading it.
* If a loader with the same id has previously been
* started it will automatically be destroyed when the new loader completes
* its work. The callback will be delivered before the old loader
* is destroyed.
+ *
+ * @param id A unique identifier for this loader. Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param args Optional arguments to supply to the loader at construction.
+ * @param callback Interface the LoaderManager will call to report about
+ * changes in the state of the loader. Required.
*/
public abstract <D> Loader<D> restartLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
@@ -86,7 +149,15 @@ public abstract class LoaderManager {
/**
* Stops and removes the loader with the given ID.
*/
- public abstract void stopLoader(int id);
+ public abstract void destroyLoader(int id);
+
+ /**
+ * @deprecated Renamed to {@link #destroyLoader}.
+ */
+ @Deprecated
+ public void stopLoader(int id) {
+ destroyLoader(id);
+ }
/**
* Return the Loader with the given id or null if no matching Loader
@@ -191,13 +262,16 @@ class LoaderManagerImpl extends LoaderManager {
stop();
}
}
- if (mStarted && mData != null) {
- // This loader was retained, and now at the point of
- // finishing the retain we find we remain started, have
- // our data, and the owner has a new callback... so
- // let's deliver the data now.
- callOnLoadFinished(mLoader, mData);
- }
+ }
+
+ if (mStarted && mData != null) {
+ // This loader has retained its data, either completely across
+ // a configuration change or just whatever the last data set
+ // was after being restarted from a stop, and now at the point of
+ // finishing the retain we find we remain started, have
+ // our data, and the owner has a new callback... so
+ // let's deliver the data now.
+ callOnLoadFinished(mLoader, mData);
}
}
@@ -211,20 +285,34 @@ class LoaderManagerImpl extends LoaderManager {
mLoader.unregisterListener(this);
mLoader.stopLoading();
}
- mData = null;
}
}
void destroy() {
if (DEBUG) Log.v(TAG, " Destroying: " + this);
mDestroyed = true;
+ if (mCallbacks != null && mLoader != null && mData != null) {
+ String lastBecause = null;
+ if (mActivity != null) {
+ lastBecause = mActivity.mFragments.mNoTransactionsBecause;
+ mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset";
+ }
+ try {
+ mCallbacks.onLoaderReset(mLoader);
+ } finally {
+ if (mActivity != null) {
+ mActivity.mFragments.mNoTransactionsBecause = lastBecause;
+ }
+ }
+ }
mCallbacks = null;
+ mData = null;
if (mLoader != null) {
if (mListenerRegistered) {
mListenerRegistered = false;
mLoader.unregisterListener(this);
}
- mLoader.destroy();
+ mLoader.reset();
}
}
@@ -238,7 +326,9 @@ class LoaderManagerImpl extends LoaderManager {
// Notify of the new data so the app can switch out the old data before
// we try to destroy it.
mData = data;
- callOnLoadFinished(loader, data);
+ if (mStarted) {
+ callOnLoadFinished(loader, data);
+ }
if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
@@ -388,7 +478,7 @@ class LoaderManagerImpl extends LoaderManager {
return (Loader<D>)info.mLoader;
}
- public void stopLoader(int id) {
+ public void destroyLoader(int id) {
if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + id);
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
@@ -416,12 +506,13 @@ class LoaderManagerImpl extends LoaderManager {
return;
}
+ mStarted = true;
+
// Call out to sub classes so they can start their loaders
// Let the existing loaders know that we want to be notified when a load is complete
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).start();
}
- mStarted = true;
}
void doStop() {
diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java
deleted file mode 100644
index f0f58564fcb7..000000000000
--- a/core/java/android/app/LoaderManagingFragment.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app;
-
-import android.content.Loader;
-import android.os.Bundle;
-
-import java.util.HashMap;
-
-/**
- * A Fragment that has utility methods for managing {@link Loader}s.
- *
- * @param <D> The type of data returned by the Loader. If you're using multiple Loaders with
- * different return types use Object and case the results.
- *
- * @deprecated This was an old design, it will be removed before Honeycomb ships.
- */
-@Deprecated
-public abstract class LoaderManagingFragment<D> extends Fragment
- implements Loader.OnLoadCompleteListener<D> {
- private boolean mStarted = false;
-
- static final class LoaderInfo<D> {
- public Bundle args;
- public Loader<D> loader;
- }
- private HashMap<Integer, LoaderInfo<D>> mLoaders;
- private HashMap<Integer, LoaderInfo<D>> mInactiveLoaders;
-
- /**
- * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
- * If a loader with the same id has previously been started it will automatically be destroyed
- * when the new loader completes it's work. The callback will be delivered before the old loader
- * is destroyed.
- */
- public Loader<D> startLoading(int id, Bundle args) {
- LoaderInfo<D> info = mLoaders.get(id);
- if (info != null) {
- // Keep track of the previous instance of this loader so we can destroy
- // it when the new one completes.
- mInactiveLoaders.put(id, info);
- }
- info = new LoaderInfo<D>();
- info.args = args;
- mLoaders.put(id, info);
- Loader<D> loader = onCreateLoader(id, args);
- info.loader = loader;
- if (mStarted) {
- // The activity will start all existing loaders in it's onStart(), so only start them
- // here if we're past that point of the activitiy's life cycle
- loader.registerListener(id, this);
- loader.startLoading();
- }
- return loader;
- }
-
- protected abstract Loader<D> onCreateLoader(int id, Bundle args);
- protected abstract void onInitializeLoaders();
- protected abstract void onLoadFinished(Loader<D> loader, D data);
-
- public final void onLoadComplete(Loader<D> loader, D data) {
- // Notify of the new data so the app can switch out the old data before
- // we try to destroy it.
- onLoadFinished(loader, data);
-
- // Look for an inactive loader and destroy it if found
- int id = loader.getId();
- LoaderInfo<D> info = mInactiveLoaders.get(id);
- if (info != null) {
- Loader<D> oldLoader = info.loader;
- if (oldLoader != null) {
- oldLoader.destroy();
- }
- mInactiveLoaders.remove(id);
- }
- }
-
- @Override
- public void onCreate(Bundle savedState) {
- super.onCreate(savedState);
-
- if (mLoaders == null) {
- // Look for a passed along loader and create a new one if it's not there
-// TODO: uncomment once getLastNonConfigurationInstance method is available
-// mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
- if (mLoaders == null) {
- mLoaders = new HashMap<Integer, LoaderInfo<D>>();
- onInitializeLoaders();
- }
- }
- if (mInactiveLoaders == null) {
- mInactiveLoaders = new HashMap<Integer, LoaderInfo<D>>();
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- // Call out to sub classes so they can start their loaders
- // Let the existing loaders know that we want to be notified when a load is complete
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- int id = entry.getKey();
- if (loader == null) {
- loader = onCreateLoader(id, info.args);
- info.loader = loader;
- }
- loader.registerListener(id, this);
- loader.startLoading();
- }
-
- mStarted = true;
- }
-
- @Override
- public void onStop() {
- super.onStop();
-
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- if (loader == null) {
- continue;
- }
-
- // Let the loader know we're done with it
- loader.unregisterListener(this);
-
- // The loader isn't getting passed along to the next instance so ask it to stop loading
- if (!getActivity().isChangingConfigurations()) {
- loader.stopLoading();
- }
- }
-
- mStarted = false;
- }
-
- /* TO DO: This needs to be turned into a retained fragment.
- @Override
- public Object onRetainNonConfigurationInstance() {
- // Pass the loader along to the next guy
- Object result = mLoaders;
- mLoaders = null;
- return result;
- }
- **/
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mLoaders != null) {
- for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
- LoaderInfo<D> info = entry.getValue();
- Loader<D> loader = info.loader;
- if (loader == null) {
- continue;
- }
- loader.destroy();
- }
- }
- }
-
- /**
- * Stops and removes the loader with the given ID.
- */
- public void stopLoading(int id) {
- if (mLoaders != null) {
- LoaderInfo<D> info = mLoaders.remove(id);
- if (info != null) {
- Loader<D> loader = info.loader;
- if (loader != null) {
- loader.unregisterListener(this);
- loader.destroy();
- }
- }
- }
- }
-
- /**
- * @return the Loader with the given id or null if no matching Loader
- * is found.
- */
- public Loader<D> getLoader(int id) {
- LoaderInfo<D> loaderInfo = mLoaders.get(id);
- if (loaderInfo != null) {
- return mLoaders.get(id).loader;
- }
- return null;
- }
-}
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 77768743cf94..c73f95de6134 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -27,6 +27,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
Cursor mCursor;
ForceLoadContentObserver mObserver;
boolean mStopped;
+ boolean mReset;
Uri mUri;
String[] mProjection;
String mSelection;
@@ -57,7 +58,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
- if (mStopped) {
+ if (mReset) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
@@ -66,9 +67,12 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
}
Cursor oldCursor = mCursor;
mCursor = cursor;
- super.deliverResult(cursor);
- if (oldCursor != null && !oldCursor.isClosed()) {
+ if (!mStopped) {
+ super.deliverResult(cursor);
+ }
+
+ if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
@@ -94,6 +98,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
@Override
public void startLoading() {
mStopped = false;
+ mReset = false;
if (mCursor != null) {
deliverResult(mCursor);
@@ -107,11 +112,6 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
*/
@Override
public void stopLoading() {
- if (mCursor != null && !mCursor.isClosed()) {
- mCursor.close();
- }
- mCursor = null;
-
// Attempt to cancel the current load task if possible.
cancelLoad();
@@ -127,9 +127,16 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
}
@Override
- public void destroy() {
+ public void reset() {
+ mReset = true;
+
// Ensure the loader is stopped
stopLoading();
+
+ if (mCursor != null && !mCursor.isClosed()) {
+ mCursor.close();
+ }
+ mCursor = null;
}
public Uri getUri() {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 234096a6709e..73d710337176 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -128,7 +128,7 @@ public abstract class Loader<D> {
* the data set and may deliver future callbacks if the source changes. Calling
* {@link #stopLoading} will stop the delivery of callbacks.
*
- * Must be called from the UI thread
+ * <p>Must be called from the UI thread
*/
public abstract void startLoading();
@@ -141,22 +141,34 @@ public abstract class Loader<D> {
/**
* Stops delivery of updates until the next time {@link #startLoading()} is called
*
- * Must be called from the UI thread
+ * <p>Must be called from the UI thread
*/
public abstract void stopLoading();
/**
- * Destroys the loader and frees its resources, making it unusable.
+ * 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
+ * {@link #startLoading()} may later be called at which point it must be
+ * able to start running again.
*
- * Must be called from the UI thread
+ * <p>Must be called from the UI thread
*/
- public abstract void destroy();
+ public void reset() {
+ destroy();
+ }
+
+ /**
+ * @deprecated Old API, implement reset() now.
+ */
+ @Deprecated
+ public void destroy() {
+ }
/**
* Called when {@link ForceLoadContentObserver} detects a change. Calls {@link #forceLoad()}
* by default.
*
- * Must be called from the UI thread
+ * <p>Must be called from the UI thread
*/
public void onContentChanged() {
forceLoad();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5385cd931ab6..8446a8fd29be 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -576,14 +576,16 @@ public abstract class Window {
/**
* Set the width and height layout parameters of the window. The default
- * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT to
- * make a window that is not full-screen.
+ * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT
+ * or an absolute value to make a window that is not full-screen.
*
* @param width The desired layout width of the window.
* @param height The desired layout height of the window.
+ *
+ * @see ViewGroup.LayoutParams#height
+ * @see ViewGroup.LayoutParams#width
*/
- public void setLayout(int width, int height)
- {
+ public void setLayout(int width, int height) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.width = width;
attrs.height = height;
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 4cf8785e5a1a..516162aab9a4 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -67,7 +67,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
- protected DataSetObserver mDataSetObserver = new MyDataSetObserver();
+ protected DataSetObserver mDataSetObserver;
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -81,54 +81,81 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
/**
* If set the adapter will call requery() on the cursor whenever a content change
- * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+ * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
+ *
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*/
+ @Deprecated
public static final int FLAG_AUTO_REQUERY = 0x01;
/**
* If set the adapter will register a content observer on the cursor and will call
- * {@link #onContentChanged()} when a notification comes in.
+ * {@link #onContentChanged()} when a notification comes in. Be careful when
+ * using this flag: you will need to unset the current Cursor from the adapter
+ * to avoid leaks due to its registered observers. This flag is not needed
+ * when using a CursorAdapter with a
+ * {@link android.content.CursorLoader}.
*/
public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
/**
- * Constructor. The adapter will call requery() on the cursor whenever
- * it changes so that the most recent data is always displayed.
+ * Constructor that always enables auto-requery.
+ *
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*
* @param c The cursor from which to get the data.
* @param context The context
*/
+ @Deprecated
public CursorAdapter(Context context, Cursor c) {
init(context, c, FLAG_AUTO_REQUERY);
}
/**
- * Constructor
+ * Constructor that allows control over auto-requery. It is recommended
+ * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
+ * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+ * will always be set.
+ *
* @param c The cursor from which to get the data.
* @param context The context
* @param autoRequery If true the adapter will call requery() on the
* cursor whenever it changes so the most recent
- * data is always displayed.
+ * data is always displayed. Using true here is discouraged.
*/
public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
}
/**
- * Constructor
+ * Recommended constructor.
+ *
* @param c The cursor from which to get the data.
* @param context The context
- * @param flags flags used to determine the behavior of the adapter
+ * @param flags Flags used to determine the behavior of the adapter; may
+ * be any combination of {@link #FLAG_AUTO_REQUERY} and
+ * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
*/
public CursorAdapter(Context context, Cursor c, int flags) {
init(context, c, flags);
}
+ /**
+ * @deprecated Don't use this, use the normal constructor. This will
+ * be removed in the future.
+ */
+ @Deprecated
protected void init(Context context, Cursor c, boolean autoRequery) {
init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
}
- protected void init(Context context, Cursor c, int flags) {
+ void init(Context context, Cursor c, int flags) {
if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
flags |= FLAG_REGISTER_CONTENT_OBSERVER;
mAutoRequery = true;
@@ -142,13 +169,15 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
mChangeObserver = new ChangeObserver();
+ mDataSetObserver = new MyDataSetObserver();
} else {
mChangeObserver = null;
+ mDataSetObserver = null;
}
if (cursorPresent) {
if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
- c.registerDataSetObserver(mDataSetObserver);
+ if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
}
}
@@ -275,22 +304,39 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*
- * @param cursor the new cursor to be used
+ * @param cursor The new cursor to be used
*/
public void changeCursor(Cursor cursor) {
- if (cursor == mCursor) {
- return;
+ Cursor old = swapCursor(cursor);
+ if (old != null) {
+ old.close();
+ }
+ }
+
+ /**
+ * Swap in a new Cursor, returning the old Cursor. Unlike
+ * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
+ * closed.
+ *
+ * @param newCursor The new cursor to be used.
+ * @return Returns the previously set Cursor, or null if there wasa not one.
+ * If the given new Cursor is the same instance is the previously set
+ * Cursor, null is also returned.
+ */
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
}
- if (mCursor != null) {
- if (mChangeObserver != null) mCursor.unregisterContentObserver(mChangeObserver);
- mCursor.unregisterDataSetObserver(mDataSetObserver);
- mCursor.close();
+ Cursor oldCursor = mCursor;
+ if (oldCursor != null) {
+ if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
- mCursor = cursor;
- if (cursor != null) {
- if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver);
- cursor.registerDataSetObserver(mDataSetObserver);
- mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
+ mCursor = newCursor;
+ if (newCursor != null) {
+ if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
+ mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataSetChanged();
@@ -300,6 +346,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
// notify the observers about the lack of a data set
notifyDataSetInvalidated();
}
+ return oldCursor;
}
/**
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index aee411ee4076..7341c2c5d319 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -35,13 +35,19 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
private LayoutInflater mInflater;
/**
- * Constructor.
+ * Constructor the enables auto-requery.
+ *
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
*/
+ @Deprecated
public ResourceCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
@@ -49,7 +55,11 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
}
/**
- * Constructor.
+ * Constructor with default behavior as per
+ * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
+ * you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
+ * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+ * will always be set.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
@@ -58,7 +68,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
* @param c The cursor from which to get the data.
* @param autoRequery If true the adapter will call requery() on the
* cursor whenever it changes so the most recent
- * data is always displayed.
+ * data is always displayed. Using true here is discouraged.
*/
public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
@@ -67,14 +77,15 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
}
/**
- * Constructor.
+ * Standard constructor.
*
* @param context The context where the ListView associated with this adapter is running
- * @param layout resource identifier of a layout file that defines the views
+ * @param layout Resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
- * @param flags flags used to determine the behavior of the adapter
+ * @param flags Flags used to determine the behavior of the adapter,
+ * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public ResourceCursorAdapter(Context context, int layout, Cursor c, int flags) {
super(context, c, flags);
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index d1c2270fada8..497610c9d02f 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -66,7 +66,23 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
String[] mOriginalFrom;
/**
- * Constructor.
+ * Constructor the enables auto-requery.
+ *
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+ */
+ @Deprecated
+ public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
+ super(context, layout, c);
+ mTo = to;
+ mOriginalFrom = from;
+ findColumns(from);
+ }
+
+ /**
+ * Standard constructor.
*
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
@@ -80,9 +96,12 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
+ * @param flags Flags used to determine the behavior of the adapter,
+ * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
- public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
- super(context, layout, c);
+ public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from,
+ int[] to, int flags) {
+ super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
findColumns(from);
diff --git a/test-runner/src/android/test/LoaderTestCase.java b/test-runner/src/android/test/LoaderTestCase.java
index 8be6590402a3..c8564c2e3540 100644
--- a/test-runner/src/android/test/LoaderTestCase.java
+++ b/test-runner/src/android/test/LoaderTestCase.java
@@ -64,7 +64,7 @@ public class LoaderTestCase extends AndroidTestCase {
// Shut the loader down
completedLoader.unregisterListener(this);
completedLoader.stopLoading();
- completedLoader.destroy();
+ completedLoader.reset();
// Store the result, unblocking the test thread
queue.add(data);