Android Loaders : Reloaded


Published on

All you ever wanted to know about Android Loaders and never dared to ask.

Important: I no longer recommend to use a Loader for "one-shot" actions because it's complicated and has a few side-effects. So I recommend to still use AsyncTasks in that case. You can create an AsyncTask inside a Fragment with setRetainInstance(true) to keep the same AsyncTask instance accross configuration changes, but beware not to update the view or interact with the Activity if the result arrives while the fragment is stopped. If you don't need the result, a static AsyncTask will do the job.

Published in: Technology
  • @JiahaoLiuLiu It's on my gist:
    Are you sure you want to  Yes  No
    Your message goes here
  • I was looking for the class SimpleCursorLoader but I couldn't find it. Could you tell me where is the class?
    Are you sure you want to  Yes  No
    Your message goes here
  • >Emanuele RIcci The loader is initialized for example when you press a button. But you need to add this code to make sure you reconnect to the loader after a configuration change which causes the Activity to be recreated.
    Are you sure you want to  Yes  No
    Your message goes here
  • if you do

    if( getLoaderManager().getLoader( LOADER_ID ) != null ) {
    getLoaderManager().initLoader(LOADER_ID, null, this);

    when will the loader inited? I think that the first time the getLoaderManager().getLoader( LOADER_ID ) will always be null...
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total Views
On Slideshare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Android Loaders : Reloaded

  1. 1. Android LoadersR E L O A D E DChristophe BeylsBrussels GTUG13th march 2013 READY.LOAD "*",8,1SEARCHING FOR *LOADINGREADY.RUN▀
  2. 2. About the speaker● Developer living in Brussels.● Likes coding, hacking devices,travelling, movies, music, (LOL)cats.● Programs mainly in Java and C#.● Uses the Android SDK nearly everyday at work.@BladeCoder
  3. 3. About the speaker
  4. 4. (Big) Agenda● A bit of History: from Threads to Loaders● Introduction to Loaders● Using the LoaderManager● Avoiding common mistakes● Implementing a basic Loader● More Loader examples● Databases and CursorLoaders● Overcoming Loaders limitations
  5. 5. A bit of History1. Plain Threadsfinal Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch(msg.what) {case RESULT_WHAT:handleResult((Result) msg.obj);return true;}return false;}});Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Result result = doStuff();if (isResumed()) {handler.sendMessage(handler.obtainMessage(RESULT_WHAT, result));}}});thread.start();
  6. 6. A bit of History1. Plain ThreadsDifficulties:● Requires you to post the result back on themain thread;● Cancellation must be handled manually;● Want a thread pool?You need to implement it yourself.
  7. 7. A bit of History2. AsyncTask (Androids SwingWorker)● Handles thread switching for you : result isposted to the main thread.● Manages scheduling for you.● Handles cancellation: if you call cancel(),onPostExecute() will not be called.● Allows to report progress.
  8. 8. A bit of History2. AsyncTaskprivate class DownloadFilesTask extends AsyncTask<Void, Integer, Result> {@Overrideprotected void onPreExecute() {// Something like showing a progress bar}@Overrideprotected Result doInBackground(Void... params) {Result result = new Result();for (int i = 0; i < STEPS; i++) {result.add(doStuff());publishProgress(100 * i / STEPS);}return result;}@Overrideprotected void onProgressUpdate(Integer... progress) {setProgressPercent(progress[0]);}@Overrideprotected void onPostExecute(Result result) {handleResult(result);}}
  9. 9. A bit of History2. AsyncTaskProblems:● You need to keep a reference to each runningAsyncTask to be able to cancel it when yourActivity is destroyed.● Memory leaks: as long as the AsyncTask runs, itkeeps a reference to its enclosing Activity even ifthe Activity has already been destroyed.● Results arriving after the Activity has beenrecreated (orientation change) are lost.
  10. 10. A bit of History2. AsyncTaskA less known but big problem.Demo
  11. 11. A bit of History2. AsyncTaskAsyncTask scheduling varies between Android versions:● Before 1.6, they run in sequence on a single thread.● From 1.6 to 2.3, they run in parallel on a thread pool.● Since 3.0, back to the old behaviour by default! Theyrun in sequence, unless you execute them withexecuteOnExecutor() with aThreadPoolExecutor.→ No parallelization by default on modern phones.
  12. 12. A bit of History2. AsyncTaskA workaround:1. public class ConcurrentAsyncTask {2. public static void execute(AsyncTask as) {3. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {4. as.execute();5. } else {6. as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);7. }8. }9. }... but you should really use Loaders instead.
  13. 13. Loaders to the rescue● Allows an Activity or Fragment to reconnect to thesame Loader after recreation and retrieve the lastresult.● If the result comes after a Loader has beendisconnected from an Activity/Fragment, it can keep itin cache to deliver it when reconnected to the recreatedActivity/Fragment.● A Loader monitors its data source and delivers newresults when the content changes.● Loaders handle allocation/disallocation of resourcesassociated with the result (example: Cursors).
  14. 14. Loaders to the rescueIf you need to perform anykind of asynchronous loadin an Activity or Fragment,you must never useAsyncTask again.And dont do like this manbecause Loaders are muchmore than justCursorLoaders.
  15. 15. Using the LoaderManager● Simple API to allow Activities and Fragments tointeract with Loaders.● One instance of LoaderManager for each Activityand each Fragment. They dont share Loaders.● Main methods:○ initLoader(int id, Bundle args,LoaderCallbacks<D> callbacks)○ restartLoader(int id, Bundle args,LoaderCallbacks<D> callbacks)○ destroyLoader(int id)○ getLoader(int id)
  16. 16. Using the LoaderManagerprivate final LoaderCallbacks<Result> loaderCallbacks= new LoaderCallbacks<Result>() {@Overridepublic Loader<Result> onCreateLoader(int id, Bundle args) {return new MyLoader(getActivity(), args.getLong("id"));}@Overridepublic void onLoadFinished(Loader<Result> loader, Result result) {handleResult(result);}@Overridepublic void onLoaderReset(Loader<Result> loader) {}};Never call a standard Loader method yourself directly onthe Loader. Always use the LoaderManager.
  17. 17. Using the LoaderManagerWhen to init Loaders at Activity/Fragment startupActivities@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);...getSupportLoaderManager().initLoader(LOADER_ID, null, callbacks);}Fragments@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);...getLoaderManager().initLoader(LOADER_ID, null, callbacks);}
  18. 18. Loaders lifecycleA loader has 3 states:● Started● Stopped● ResetThe LoaderManager automatically changesthe state of the Loaders according to theActivity or Fragment state.
  19. 19. Loaders lifecycle● Activity/Fragment starts→ Loader starts: onStartLoading()● Activity becomes invisible or Fragment is detached→ Loader stops: onStopLoading()● Activity/Fragment is recreated → no callback.The LoaderManager will continue to receive theresults and keep them in a local cache.● Activity/Fragment is destroyedor restartLoader() is calledor destroyLoader() is called→ Loader resets: onReset()
  20. 20. Passing argumentsUsing args Bundleprivate void onNewQuery(String query) {Bundle args = new Bundle();args.putString("query", query);getLoaderManager().restartLoader(LOADER_ID, args,loaderCallbacks);}@Overridepublic Loader<Result> onCreateLoader(int id, Bundle args) {return new QueryLoader(getActivity(), args.getString("query"));}
  21. 21. Passing argumentsUsing args Bundle● You dont need to use the args param most of the time.Pass null.● The loaderCallBacks is part of your Fragment/Activity.You can access your Fragment/Activity instancevariables too.@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.newsId = getArguments().getLong("newsId");}...@Overridepublic Loader<News> onCreateLoader(int id, Bundle args) {return new NewsLoader(getActivity(), NewsFragment.this.newsId);}
  22. 22. A LoaderManager bugWhen a Fragment is recreated on configurationchange, its LoaderManager calls theonLoadFinished() callback twice to send back thelast result of the Loader when you callinitLoader() in onActivityCreated().3 possible workarounds:1. Dont do anything. If your code permits it.2. Save the previous result and check if its different.3. Call setRetainInstance(true) in onCreate().
  23. 23. One-shot LoadersSometimes you only want to perform a loaderaction once.Example: submitting a form.● You call initLoader() in response to an action.● You need to reconnect to the loader onorientation change to get the result.
  24. 24. One-shot LoadersIn your LoaderCallbacks@Overridepublic void onLoadFinished(Loader<Integer> loader, Result result) {getLoaderManager().destroyLoader(LOADER_ID);... // Process the result}On Activity/Fragment creation@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onCreate(savedInstanceState);...// Reconnect to the loader only if presentif (getLoaderManager().getLoader(LOADER_ID) != null) {getLoaderManager().initLoader(LOADER_ID, null, this);}}
  25. 25. Common mistakes1. Dont go crazy with loader idsYou dont need to:● Increment the loader id or choose a random loader ideach time you initialize or restart a Loader.This will prevent your Loaders from being reused andwill create a complete mess!Use a single unique id for each kind of Loader.
  26. 26. Common mistakes1. Dont go crazy with loader idsYou dont need to:● Create a loader id constant for each andevery kind of Loader accross your entire app.Each LoaderManager is independent.Just create private constants in your Activityor Fragment for each kind of loader in it.
  27. 27. Common mistakes2. Avoid FragmentManager ExceptionsYou can not create a FragmentTransactiondirectly in LoaderCallbacks.This includes any dialog you create as aDialogFragment.Solution: Use a Handler to dispatch theFragmentTransaction.
  28. 28. Common mistakes2. Avoid FragmentManager Exceptionspublic class LinesFragment extends ContextMenuSherlockListFragment implementsLoaderCallbacks<List<LineInfo>>, Callback {...@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);handler = new Handler(this);adapter = new LinesAdapter(getActivity());}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);setListAdapter(adapter);setListShown(false);getLoaderManager().initLoader(LINES_LOADER_ID, null, this);}
  29. 29. Common mistakes2. Avoid FragmentManager Exceptions@Overridepublic Loader<List<LineInfo>> onCreateLoader(int id, Bundle args) {return new LinesLoader(getActivity());}@Overridepublic void onLoadFinished(Loader<List<LineInfo>> loader, List<LineInfo> data) {if (data != null) {adapter.setLinesList(data);} else if (isResumed()) {handler.sendEmptyMessage(LINES_LOADING_ERROR_WHAT);}// The list should now be shown.if (isResumed()) {setListShown(true);} else {setListShownNoAnimation(true);}}
  30. 30. Common mistakes2. Avoid FragmentManager Exceptions@Overridepublic void onLoaderReset(Loader<List<LineInfo>> loader) {adapter.setLinesList(null);}@Overridepublic boolean handleMessage(Message message) {switch (message.what) {case LINES_LOADING_ERROR_WHAT:MessageDialogFragment.newInstance(R.string.error_title, R.string.lines_loading_error).show(getFragmentManager());return true;}return false;}
  31. 31. Implementing a basic Loader3 classes provided by the support library:● LoaderBase abstract class.● AsyncTaskLoaderAbstract class, extends Loader.● CursorLoaderExtends AsyncTaskLoader.Particular implementation dedicated toquerying ContentProviders.
  32. 32. AsyncTaskLoaderDoes it suffer from AsyncTasks limitations?
  33. 33. AsyncTaskLoaderDoes it suffer from AsyncTasks limitations?No, because it uses ModernAsyncTaskinternally, which has the same implementationon each Android version.private static final int CORE_POOL_SIZE = 5;private static final int MAXIMUM_POOL_SIZE = 128;private static final int KEEP_ALIVE = 1;public static final Executor THREAD_POOL_EXECUTOR= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);private static volatile Executor sDefaultExecutor = THREAD_POOL_EXECUTOR;
  34. 34. Implementing a basic LoaderIMPORTANT: Avoid memory leaksBy design, Loaders only keep a reference to theApplication context so there is no leak:/*** Stores away the application context associated with context. Since Loaders can be* used across multiple activities its dangerous to store the context directly.** @param context used to retrieve the application context.*/public Loader(Context context) {mContext = context.getApplicationContext();}But each of your Loader inner classes must bedeclared static or they will keep an implicitreference to their parent!
  35. 35. Implementing a basic LoaderWe need to extend AsyncTaskLoader andimplement its behavior.
  36. 36. Implementing a basic LoaderThe callbacks to implementMandatory● onStartLoading()● onStopLoading()● onReset()● onForceLoad() from Loader ORloadInBackground() from AsyncTaskLoaderOptional● deliverResult() [override]
  37. 37. Implementing a basic Loaderpublic abstract class BasicLoader<T> extends AsyncTaskLoader<T> {public BasicLoader(Context context) {super(context);}@Overrideprotected void onStartLoading() {forceLoad(); // Launch the background task}@Overrideprotected void onStopLoading() {cancelLoad(); // Attempt to cancel the current load task if possible}@Overrideprotected void onReset() {super.onReset();onStopLoading();}}
  38. 38. Implementing a basic Loaderpublic abstract class LocalCacheLoader<T> extends AsyncTaskLoader<T> {private T mResult;public AbstractAsyncTaskLoader(Context context) {super(context);}@Overrideprotected void onStartLoading() {if (mResult != null) {// If we currently have a result available, deliver it// immediately.deliverResult(mResult);}if (takeContentChanged() || mResult == null) {// If the data has changed since the last time it was loaded// or is not currently available, start a load.forceLoad();}}...
  39. 39. Implementing a basic Loader@Overrideprotected void onStopLoading() {// Attempt to cancel the current load task if possible.cancelLoad();}@Overrideprotected void onReset() {super.onReset();onStopLoading();mResult = null;}@Overridepublic void deliverResult(T data) {mResult = data;if (isStarted()) {// If the Loader is currently started, we can immediately// deliver its results.super.deliverResult(data);}}}
  40. 40. Implementing a basic LoaderWhat about a global cache instead?public abstract class GlobalCacheLoader<T> extends AsyncTaskLoader<T> {...@Overrideprotected void onStartLoading() {T cachedResult = getCachedResult();if (cachedResult != null) {// If we currently have a result available, deliver it// immediately.deliverResult(cachedResult);}if (takeContentChanged() || cachedResult == null) {// If the data has changed since the last time it was loaded// or is not currently available, start a load.forceLoad();}}...protected abstract T getCachedResult();}
  41. 41. Monitoring dataTwo Loader methods to help● onContentChanged()If the Loader is started: will call forceLoad().If the Loader is stopped: will set a flag.● takeContentChanged()Returns the flag value and clears the flag.@Overrideprotected void onStartLoading() {if (mResult != null) {deliverResult(mResult);}if (takeContentChanged() || mResult == null) {forceLoad();}}
  42. 42. AutoRefreshLoaderpublic abstract class AutoRefreshLoader<T> extends LocalCacheLoader<T> {private long interval;private Handler handler;private final Runnable timeoutRunnable = new Runnable() {@Overridepublic void run() {onContentChanged();}};public AutoRefreshLoader(Context context, long interval) {super(context);this.interval = interval;this.handler = new Handler();}...
  43. 43. AutoRefreshLoader...@Overrideprotected void onForceLoad() {super.onForceLoad();handler.removeCallbacks(timeoutRunnable);handler.postDelayed(timeoutRunnable, interval);}@Overridepublic void onCanceled(T data) {super.onCanceled(data);// Retry a refresh the next time the loader is startedonContentChanged();}@Overrideprotected void onReset() {super.onReset();handler.removeCallbacks(timeoutRunnable);}}
  44. 44. CursorLoaderCursorLoader is a Loader dedicated to queryingContentProviders● It returns a database Cursor as result.● It performs the database query on a backgroundthread (it inherits from AsyncTaskLoader).● It replaces Activity.startManagingCursor(Cursor c)It manages the Cursor lifecycle according to theActivity Lifecycle. → Never call close()● It monitors the database and returns a new cursorwhen data has changed. → Never call requery()
  45. 45. CursorLoaderUsage with a CursorAdapter in a ListFragment@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {return new BookmarksLoader(getActivity(),args.getDouble("latitude"), args.getDouble("longitude"));}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {adapter.swapCursor(data);// The list should now be shown.if (isResumed()) {setListShown(true);} else {setListShownNoAnimation(true);}}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {adapter.swapCursor(null);}
  46. 46. CursorLoader
  47. 47. SimpleCursorLoaderIf you dont need the complexity of a ContentProvider... but want to access a local database anyway● SimpleCursorLoader is an abstract class based onCursorLoader with all the ContentProvider-specificstuff removed.● You just need to override one method whichperforms the actual database query.
  48. 48. SimpleCursorLoaderExample usage - bookmarksprivate static class BookmarksLoader extends SimpleCursorLoader {private double latitude;private double longitude;public BookmarksLoader(Context context, double latitude, double longitude) {super(context);this.latitude = latitude;this.longitude = longitude;}@Overrideprotected Cursor getCursor() {return DatabaseManager.getInstance().getBookmarks(latitude, longitude);}}
  49. 49. SimpleCursorLoaderExample usage - bookmarkspublic class DatabaseManager {private static final Uri URI_BOOKMARKS =Uri.parse("sqlite://");...public Cursor getBookmarks(double latitude, double longitude) {// A big database query you dont want to see...cursor.setNotificationUri(context.getContentResolver(), URI_BOOKMARKS);return cursor;}...
  50. 50. SimpleCursorLoaderExample usage - bookmarks...public boolean addBookmark(Bookmark bookmark) {SQLiteDatabase db = helper.getWritableDatabase();db.beginTransaction();try {// Other database stuff you dont want to see...long result = db.insert(DatabaseHelper.BOOKMARKS_TABLE_NAME, null,values);db.setTransactionSuccessful();// Will return -1 if the bookmark was already presentreturn result != -1L;} finally {db.endTransaction();context.getContentResolver().notifyChange(URI_BOOKMARKS, null);}}}
  51. 51. Loaders limitations
  52. 52. Loaders limitations1. No built-in progress updates supportWorkaround: use LocalBroadcastManager.In the Activity:@Overrideprotected void onStart() {// Receive loading status broadcasts in order to update the progress barLocalBroadcastManager.getInstance(this).registerReceiver(loadingStatusReceiver,new IntentFilter(MyLoader.LOADING_ACTION));super.onStart();}@Overrideprotected void onStop() {super.onStop();LocalBroadcastManager.getInstance(this).unregisterReceiver(loadingStatusReceiver);}
  53. 53. Loaders limitations1. No built-in progress updates supportWorkaround: use LocalBroadcastManager.In the Loader:@Overridepublic Result loadInBackground() {// Show progress barIntent intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, true);LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);try {return doStuff();} finally {// Hide progress barintent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, false);LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);}}
  54. 54. Loaders limitations2. No error handling in LoaderCallbacks.You usually simply return null in case of error.Possible Workarounds:1) Encapsulate the result along with an Exception in acomposite Object like Pair<T, Exception>.Warning: your Loaders cache must be smarter andcheck if the object is null or contains an error.2) Add a property to your Loader to expose a catchedException.
  55. 55. Loaders limitationspublic abstract class ExceptionSupportLoader<T> extends LocalCacheLoader<T> {private Exception lastException;public ExceptionSupportLoader(Context context) {super(context);}public Exception getLastException() {return lastException;}@Overridepublic T loadInBackground() {try {return tryLoadInBackground();} catch (Exception e) {this.lastException = e;return null;}}protected abstract T tryLoadInBackground() throws Exception;}
  56. 56. Loaders limitations2. No error handling in LoaderCallbacks.Workaround #2 (end)Then in your LoaderCallbacks:@Overridepublic void onLoadFinished(Loader<Result> loader, Result result) {if (result == null) {Exception exception = ((ExceptionSupportLoader<Result>) loader).getLastException();// Error handling} else {// Result handling}}
  57. 57. The EndWe made it!Thank you for watching.Questions?
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.