example ejemplo android android-loadermanager asynctaskloader android-loader

ejemplo - android loader example



AsyncTaskLoader onLoadFinished con una tarea pendiente y cambio de configuraciĆ³n (4)

En la mayoría de los casos, simplemente debe ignorar dichos informes si la Actividad ya está destruida.

public void onLoadFinished(Loader<String> loader, String data) { Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId); if (isDestroyed()) { Log.i("DemoActivity", "Activity already destroyed, report ignored: " + data); return; } resultFragment.setResultText(data); }

También debe insertar la comprobación isDestroyed() en cualquier clase interna. Runnable - es el caso más usado.

Por ejemplo:

// UI thread final Handler handler = new Handler(); Executor someExecutorService = ... ; someExecutorService.execute(new Runnable() { public void run() { // some heavy operations ... // notification to UI thread handler.post(new Runnable() { // this runnable can link to ''dead'' activity or any outer instance if (isDestroyed()) { return; } // we are alive onSomeHeavyOperationFinished(); }); } });

Pero en tales casos, la mejor manera es evitar pasar referencias fuertes en Actividad a otro hilo (AsynkTask, Loader, Executor, etc.).

La solución más confiable está aquí:

// BackgroundExecutor.java public class BackgroundExecutor { private static final Executor instance = Executors.newSingleThreadExecutor(); public static void execute(Runnable command) { instance.execute(command); } } // MyActivity.java public class MyActivity extends Activity { // Some callback method from any button you want public void onSomeButtonClicked() { // Show toast or progress bar if needed // Start your heavy operation BackgroundExecutor.execute(new SomeHeavyOperation(this)); } public void onSomeHeavyOperationFinished() { if (isDestroyed()) { return; } // Hide progress bar, update UI } } // SomeHeavyOperation.java public class SomeHeavyOperation implements Runnable { private final WeakReference<MyActivity> ref; public SomeHeavyOperation(MyActivity owner) { // Unlike inner class we do not store strong reference to Activity here this.ref = new WeakReference<MyActivity>(owner); } public void run() { // Perform your heavy operation // ... // Done! // It''s time to notify Activity final MyActivity owner = ref.get(); // Already died reference if (owner == null) return; // Perform notification in UI thread owner.runOnUiThread(new Runnable() { public void run() { owner.onSomeHeavyOperationFinished(); } }); } }

Estoy tratando de usar un AsyncTaskLoader para cargar datos en segundo plano para completar una vista detallada en respuesta a un elemento de lista que se está seleccionando. Lo tengo funcionando en su mayoría, pero todavía tengo un problema. Si elijo un segundo elemento en la lista y luego roto el dispositivo antes de que se complete la carga del primer elemento seleccionado , entonces la llamada onLoadFinished() informa a la actividad que se está deteniendo en lugar de a la nueva actividad. Esto funciona bien cuando se elige un solo elemento y luego se gira.

Aquí está el código que estoy usando. Actividad:

public final class DemoActivity extends Activity implements NumberListFragment.RowTappedListener, LoaderManager.LoaderCallbacks<String> { private static final AtomicInteger activityCounter = new AtomicInteger(0); private int myActivityId; private ResultFragment resultFragment; private Integer selectedNumber; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myActivityId = activityCounter.incrementAndGet(); Log.d("DemoActivity", "onCreate for " + myActivityId); setContentView(R.layout.demo); resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment); getLoaderManager().initLoader(0, null, this); } @Override protected void onDestroy() { super.onDestroy(); Log.d("DemoActivity", "onDestroy for " + myActivityId); } @Override public void onRowTapped(Integer number) { selectedNumber = number; resultFragment.setResultText("Fetching details for item " + number + "..."); getLoaderManager().restartLoader(0, null, this); } @Override public Loader<String> onCreateLoader(int id, Bundle args) { return new ResultLoader(this, selectedNumber); } @Override public void onLoadFinished(Loader<String> loader, String data) { Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId); resultFragment.setResultText(data); } @Override public void onLoaderReset(Loader<String> loader) { } static final class ResultLoader extends AsyncTaskLoader<String> { private static final Random random = new Random(); private final Integer number; private String result; ResultLoader(Context context, Integer number) { super(context); this.number = number; } @Override public String loadInBackground() { // Simulate expensive Web call try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000); } @Override public void deliverResult(String data) { if (isReset()) { // An async query came in while the loader is stopped return; } result = data; if (isStarted()) { super.deliverResult(data); } } @Override protected void onStartLoading() { if (result != null) { deliverResult(result); } // Only do a load if we have a source to load from if (number != null) { forceLoad(); } } @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); result = null; } } }

Fragmento de lista:

public final class NumberListFragment extends ListFragment { interface RowTappedListener { void onRowTapped(Integer number); } private RowTappedListener rowTappedListener; @Override public void onAttach(Activity activity) { super.onAttach(activity); rowTappedListener = (RowTappedListener) activity; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(getActivity(), R.layout.simple_list_item_1, Arrays.asList(1, 2, 3, 4, 5, 6)); setListAdapter(adapter); } @Override public void onListItemClick(ListView l, View v, int position, long id) { ArrayAdapter<Integer> adapter = (ArrayAdapter<Integer>) getListAdapter(); rowTappedListener.onRowTapped(adapter.getItem(position)); } }

Fragmento del resultado:

public final class ResultFragment extends Fragment { private TextView resultLabel; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.result_fragment, container, false); resultLabel = (TextView) root.findViewById(R.id.result_label); if (savedInstanceState != null) { resultLabel.setText(savedInstanceState.getString("labelText", "")); } return root; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("labelText", resultLabel.getText().toString()); } void setResultText(String resultText) { resultLabel.setText(resultText); } }

He podido hacer que esto funcione utilizando AsyncTask simples, pero estoy tratando de aprender más acerca de los Loader ya que manejan los cambios de configuración automáticamente.

EDITAR : Creo que puedo haber rastreado el problema mirando la fuente de LoaderManager . Cuando se llama a initLoader después del cambio de configuración, el objeto LoaderInfo tiene su campo mCallbacks actualizado con la nueva actividad como la implementación de LoaderCallbacks , como es de esperar.

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } LoaderInfo info = mLoaders.get(id); if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); if (info == null) { // Loader doesn''t already exist; create. info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); if (DEBUG) Log.v(TAG, " Created new loader " + info); } else { if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; } if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } return (Loader<D>)info.mLoader; }

Sin embargo, cuando hay un cargador pendiente, el objeto principal LoaderInfo también tiene un campo mPendingLoader con una referencia a un LoaderCallbacks , y este objeto nunca se actualiza con la nueva actividad en el campo mCallbacks . Espero ver el código como este en su lugar:

// This line was already there info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; // This line is not currently there info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;

Parece ser debido a esto que el cargador pendiente llama onLoadFinished en la instancia de actividad anterior. Si interrumpo este método y hago la llamada que siento que está faltando al usar el depurador, todo funciona como espero.

La nueva pregunta es: ¿He encontrado un error o es este el comportamiento esperado?


Ok, estoy tratando de entender esto, disculpe si entendí mal algo, pero está perdiendo referencias a algo cuando el dispositivo gira.

Tomando una puñalada ...

sería añadir

android:configChanges="orientation|keyboardHidden|screenSize"

¿En tu manifiesto por esa actividad arregla tu error? o evitar que onLoadFinished() diga que la actividad se detuvo?


Tal vez no sea la mejor solución, pero ... Este código reinicia el cargador cada vez, lo cual es malo pero solo funciona para evitarlo, si quiere usar el cargador.

Loader l = getLoaderManager().getLoader(MY_LOADER); if (l != null) { getLoaderManager().restartLoader(MY_LOADER, null, this); } else { getLoaderManager().initLoader(MY_LOADER, null, this); }

Por cierto Estoy usando Cursorloader ...


Una posible solución es iniciar AsyncTask en un objeto singleton personalizado y acceder al resultado onFinished () del singleton dentro de su actividad. Cada vez que gire la pantalla, vaya a Pausa () u onResume (), se usará / accederá al último resultado. Si aún no tiene un resultado en su objeto singleton, sabe que todavía está ocupado o que puede reiniciar la tarea.

Otro enfoque es trabajar con un bus de servicio como Otto, o trabajar con un servicio.