SlideShare ist ein Scribd-Unternehmen logo
1 von 57
Downloaden Sie, um offline zu lesen
Android Loaders
R E L O A D E D
Christophe Beyls
Brussels GTUG
13th march 2013 READY.
LOAD "*",8,1
SEARCHING FOR *
LOADING
READY.
RUN▀
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 every
day at work.
@BladeCoder
About the speaker
(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
A bit of History
1. Plain Threads
final Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch(msg.what) {
case RESULT_WHAT:
handleResult((Result) msg.obj);
return true;
}
return false;
}
});
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Result result = doStuff();
if (isResumed()) {
handler.sendMessage(handler.obtainMessage(RESULT_WHAT, result));
}
}
});
thread.start();
A bit of History
1. Plain Threads
Difficulties:
● Requires you to post the result back on the
main thread;
● Cancellation must be handled manually;
● Want a thread pool?
You need to implement it yourself.
A bit of History
2. AsyncTask (Android's SwingWorker)
● Handles thread switching for you : result is
posted to the main thread.
● Manages scheduling for you.
● Handles cancellation: if you call cancel(),
onPostExecute() will not be called.
● Allows to report progress.
A bit of History
2. AsyncTask
private class DownloadFilesTask extends AsyncTask<Void, Integer, Result> {
@Override
protected void onPreExecute() {
// Something like showing a progress bar
}
@Override
protected Result doInBackground(Void... params) {
Result result = new Result();
for (int i = 0; i < STEPS; i++) {
result.add(doStuff());
publishProgress(100 * i / STEPS);
}
return result;
}
@Override
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
@Override
protected void onPostExecute(Result result) {
handleResult(result);
}
}
A bit of History
2. AsyncTask
Problems:
● You need to keep a reference to each running
AsyncTask to be able to cancel it when your
Activity is destroyed.
● Memory leaks: as long as the AsyncTask runs, it
keeps a reference to its enclosing Activity even if
the Activity has already been destroyed.
● Results arriving after the Activity has been
recreated (orientation change) are lost.
A bit of History
2. AsyncTask
A less known but big problem.
Demo
A bit of History
2. AsyncTask
AsyncTask 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! They
run in sequence, unless you execute them with
executeOnExecutor() with a
ThreadPoolExecutor.
→ No parallelization by default on modern phones.
A bit of History
2. AsyncTask
A 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.
Loaders to the rescue
● Allows an Activity or Fragment to reconnect to the
same Loader after recreation and retrieve the last
result.
● If the result comes after a Loader has been
disconnected from an Activity/Fragment, it can keep it
in cache to deliver it when reconnected to the recreated
Activity/Fragment.
● A Loader monitors its data source and delivers new
results when the content changes.
● Loaders handle allocation/disallocation of resources
associated with the result (example: Cursors).
Loaders to the rescue
If you need to perform any
kind of asynchronous load
in an Activity or Fragment,
you must never use
AsyncTask again.
And don't do like this man
because Loaders are much
more than just
CursorLoaders.
Using the LoaderManager
● Simple API to allow Activities and Fragments to
interact with Loaders.
● One instance of LoaderManager for each Activity
and each Fragment. They don't 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)
Using the LoaderManager
private final LoaderCallbacks<Result> loaderCallbacks
= new LoaderCallbacks<Result>() {
@Override
public Loader<Result> onCreateLoader(int id, Bundle args) {
return new MyLoader(getActivity(), args.getLong("id"));
}
@Override
public void onLoadFinished(Loader<Result> loader, Result result) {
handleResult(result);
}
@Override
public void onLoaderReset(Loader<Result> loader) {
}
};
Never call a standard Loader method yourself directly on
the Loader. Always use the LoaderManager.
Using the LoaderManager
When to init Loaders at Activity/Fragment startup
Activities
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
getSupportLoaderManager().initLoader(LOADER_ID, null, callbacks);
}
Fragments
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
getLoaderManager().initLoader(LOADER_ID, null, callbacks);
}
Loaders lifecycle
A loader has 3 states:
● Started
● Stopped
● Reset
The LoaderManager automatically changes
the state of the Loaders according to the
Activity or Fragment state.
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 the
results and keep them in a local cache.
● Activity/Fragment is destroyed
or restartLoader() is called
or destroyLoader() is called
→ Loader resets: onReset()
Passing arguments
Using args Bundle
private void onNewQuery(String query) {
Bundle args = new Bundle();
args.putString("query", query);
getLoaderManager().restartLoader(LOADER_ID, args,
loaderCallbacks);
}
@Override
public Loader<Result> onCreateLoader(int id, Bundle args) {
return new QueryLoader(getActivity(), args.getString("query"));
}
Passing arguments
Using args Bundle
● You don't 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 instance
variables too.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.newsId = getArguments().getLong("newsId");
}
...
@Override
public Loader<News> onCreateLoader(int id, Bundle args) {
return new NewsLoader(getActivity(), NewsFragment.this.newsId);
}
A LoaderManager bug
When a Fragment is recreated on configuration
change, its LoaderManager calls the
onLoadFinished() callback twice to send back the
last result of the Loader when you call
initLoader() in onActivityCreated().
3 possible workarounds:
1. Don't do anything. If your code permits it.
2. Save the previous result and check if it's different.
3. Call setRetainInstance(true) in onCreate().
One-shot Loaders
Sometimes you only want to perform a loader
action once.
Example: submitting a form.
● You call initLoader() in response to an action.
● You need to reconnect to the loader on
orientation change to get the result.
One-shot Loaders
In your LoaderCallbacks
@Override
public void onLoadFinished(Loader<Integer> loader, Result result) {
getLoaderManager().destroyLoader(LOADER_ID);
... // Process the result
}
On Activity/Fragment creation
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Reconnect to the loader only if present
if (getLoaderManager().getLoader(LOADER_ID) != null) {
getLoaderManager().initLoader(LOADER_ID, null, this);
}
}
Common mistakes
1. Don't go crazy with loader ids
You don't need to:
● Increment the loader id or choose a random loader id
each time you initialize or restart a Loader.
This will prevent your Loaders from being reused and
will create a complete mess!
Use a single unique id for each kind of Loader.
Common mistakes
1. Don't go crazy with loader ids
You don't need to:
● Create a loader id constant for each and
every kind of Loader accross your entire app.
Each LoaderManager is independent.
Just create private constants in your Activity
or Fragment for each kind of loader in it.
Common mistakes
2. Avoid FragmentManager Exceptions
You can not create a FragmentTransaction
directly in LoaderCallbacks.
This includes any dialog you create as a
DialogFragment.
Solution: Use a Handler to dispatch the
FragmentTransaction.
Common mistakes
2. Avoid FragmentManager Exceptions
public class LinesFragment extends ContextMenuSherlockListFragment implements
LoaderCallbacks<List<LineInfo>>, Callback {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler(this);
adapter = new LinesAdapter(getActivity());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
setListShown(false);
getLoaderManager().initLoader(LINES_LOADER_ID, null, this);
}
Common mistakes
2. Avoid FragmentManager Exceptions
@Override
public Loader<List<LineInfo>> onCreateLoader(int id, Bundle args) {
return new LinesLoader(getActivity());
}
@Override
public 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);
}
}
Common mistakes
2. Avoid FragmentManager Exceptions
@Override
public void onLoaderReset(Loader<List<LineInfo>> loader) {
adapter.setLinesList(null);
}
@Override
public 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;
}
Implementing a basic Loader
3 classes provided by the support library:
● Loader
Base abstract class.
● AsyncTaskLoader
Abstract class, extends Loader.
● CursorLoader
Extends AsyncTaskLoader.
Particular implementation dedicated to
querying ContentProviders.
AsyncTaskLoader
Does it suffer from AsyncTask's limitations?
AsyncTaskLoader
Does it suffer from AsyncTask's limitations?
No, because it uses ModernAsyncTask
internally, which has the same implementation
on 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;
Implementing a basic Loader
IMPORTANT: Avoid memory leaks
By design, Loaders only keep a reference to the
Application context so there is no leak:
/**
* Stores away the application context associated with context. Since Loaders can be
* used across multiple activities it's 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 be
declared static or they will keep an implicit
reference to their parent!
Implementing a basic Loader
We need to extend AsyncTaskLoader and
implement its behavior.
Implementing a basic Loader
The callbacks to implement
Mandatory
● onStartLoading()
● onStopLoading()
● onReset()
● onForceLoad() from Loader OR
loadInBackground() from AsyncTaskLoader
Optional
● deliverResult() [override]
Implementing a basic Loader
public abstract class BasicLoader<T> extends AsyncTaskLoader<T> {
public BasicLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
forceLoad(); // Launch the background task
}
@Override
protected void onStopLoading() {
cancelLoad(); // Attempt to cancel the current load task if possible
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
}
}
Implementing a basic Loader
public abstract class LocalCacheLoader<T> extends AsyncTaskLoader<T> {
private T mResult;
public AbstractAsyncTaskLoader(Context context) {
super(context);
}
@Override
protected 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();
}
}
...
Implementing a basic Loader
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
mResult = null;
}
@Override
public void deliverResult(T data) {
mResult = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(data);
}
}
}
Implementing a basic Loader
What about a global cache instead?
public abstract class GlobalCacheLoader<T> extends AsyncTaskLoader<T> {
...
@Override
protected 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();
}
Monitoring data
Two 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.
@Override
protected void onStartLoading() {
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
AutoRefreshLoader
public abstract class AutoRefreshLoader<T> extends LocalCacheLoader<T> {
private long interval;
private Handler handler;
private final Runnable timeoutRunnable = new Runnable() {
@Override
public void run() {
onContentChanged();
}
};
public AutoRefreshLoader(Context context, long interval) {
super(context);
this.interval = interval;
this.handler = new Handler();
}
...
AutoRefreshLoader
...
@Override
protected void onForceLoad() {
super.onForceLoad();
handler.removeCallbacks(timeoutRunnable);
handler.postDelayed(timeoutRunnable, interval);
}
@Override
public void onCanceled(T data) {
super.onCanceled(data);
// Retry a refresh the next time the loader is started
onContentChanged();
}
@Override
protected void onReset() {
super.onReset();
handler.removeCallbacks(timeoutRunnable);
}
}
CursorLoader
CursorLoader is a Loader dedicated to querying
ContentProviders
● It returns a database Cursor as result.
● It performs the database query on a background
thread (it inherits from AsyncTaskLoader).
● It replaces Activity.startManagingCursor(Cursor c)
It manages the Cursor lifecycle according to the
Activity Lifecycle. → Never call close()
● It monitors the database and returns a new cursor
when data has changed. → Never call requery()
CursorLoader
Usage with a CursorAdapter in a ListFragment
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new BookmarksLoader(getActivity(),
args.getDouble("latitude"), args.getDouble("longitude"));
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
CursorLoader
SimpleCursorLoader
If you don't need the complexity of a ContentProvider
... but want to access a local database anyway
● SimpleCursorLoader is an abstract class based on
CursorLoader with all the ContentProvider-specific
stuff removed.
● You just need to override one method which
performs the actual database query.
SimpleCursorLoader
Example usage - bookmarks
private 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;
}
@Override
protected Cursor getCursor() {
return DatabaseManager.getInstance().getBookmarks(latitude, longitude);
}
}
SimpleCursorLoader
Example usage - bookmarks
public class DatabaseManager {
private static final Uri URI_BOOKMARKS =
Uri.parse("sqlite://your.package.name/bookmarks");
...
public Cursor getBookmarks(double latitude, double longitude) {
// A big database query you don't want to see
...
cursor.setNotificationUri(context.getContentResolver(), URI_BOOKMARKS);
return cursor;
}
...
SimpleCursorLoader
Example usage - bookmarks
...
public boolean addBookmark(Bookmark bookmark) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
// Other database stuff you don't want to see
...
long result = db.insert(DatabaseHelper.BOOKMARKS_TABLE_NAME, null,
values);
db.setTransactionSuccessful();
// Will return -1 if the bookmark was already present
return result != -1L;
} finally {
db.endTransaction();
context.getContentResolver().notifyChange(URI_BOOKMARKS, null);
}
}
}
Loaders limitations
Loaders limitations
1. No built-in progress updates support
Workaround: use LocalBroadcastManager.
In the Activity:
@Override
protected void onStart() {
// Receive loading status broadcasts in order to update the progress bar
LocalBroadcastManager.getInstance(this).registerReceiver(loadingStatusReceiver,
new IntentFilter(MyLoader.LOADING_ACTION));
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(loadingStatusReceiver);
}
Loaders limitations
1. No built-in progress updates support
Workaround: use LocalBroadcastManager.
In the Loader:
@Override
public Result loadInBackground() {
// Show progress bar
Intent intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, true);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
try {
return doStuff();
} finally {
// Hide progress bar
intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, false);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
}
Loaders limitations
2. 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 a
composite Object like Pair<T, Exception>.
Warning: your Loader's cache must be smarter and
check if the object is null or contains an error.
2) Add a property to your Loader to expose a catched
Exception.
Loaders limitations
public abstract class ExceptionSupportLoader<T> extends LocalCacheLoader<T> {
private Exception lastException;
public ExceptionSupportLoader(Context context) {
super(context);
}
public Exception getLastException() {
return lastException;
}
@Override
public T loadInBackground() {
try {
return tryLoadInBackground();
} catch (Exception e) {
this.lastException = e;
return null;
}
}
protected abstract T tryLoadInBackground() throws Exception;
}
Loaders limitations
2. No error handling in LoaderCallbacks.
Workaround #2 (end)
Then in your LoaderCallbacks:
@Override
public void onLoadFinished(Loader<Result> loader, Result result) {
if (result == null) {
Exception exception = ((ExceptionSupportLoader<Result>) loader)
.getLastException();
// Error handling
} else {
// Result handling
}
}
The End
We made it!
Thank you for watching.
Questions?

Weitere ähnliche Inhalte

Was ist angesagt?

Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
Tatsuya Maki
 

Was ist angesagt? (20)

Maintainable JavaScript 2011
Maintainable JavaScript 2011Maintainable JavaScript 2011
Maintainable JavaScript 2011
 
droidparts
droidpartsdroidparts
droidparts
 
Code Samples
Code SamplesCode Samples
Code Samples
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroid
 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15
 
The Ring programming language version 1.2 book - Part 51 of 84
The Ring programming language version 1.2 book - Part 51 of 84The Ring programming language version 1.2 book - Part 51 of 84
The Ring programming language version 1.2 book - Part 51 of 84
 
Completable future
Completable futureCompletable future
Completable future
 
Daggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processorDaggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processor
 
Quickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop ApplicationsQuickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop Applications
 
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
 
Android Threading
Android ThreadingAndroid Threading
Android Threading
 
Demystifying dependency Injection: Dagger and Toothpick
Demystifying dependency Injection: Dagger and ToothpickDemystifying dependency Injection: Dagger and Toothpick
Demystifying dependency Injection: Dagger and Toothpick
 
Android Pro Recipes
Android Pro RecipesAndroid Pro Recipes
Android Pro Recipes
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
RxJava for Android - GDG DevFest Ukraine 2015
RxJava for Android - GDG DevFest Ukraine 2015RxJava for Android - GDG DevFest Ukraine 2015
RxJava for Android - GDG DevFest Ukraine 2015
 
RxJava on Android
RxJava on AndroidRxJava on Android
RxJava on Android
 
The Ring programming language version 1.8 book - Part 77 of 202
The Ring programming language version 1.8 book - Part 77 of 202The Ring programming language version 1.8 book - Part 77 of 202
The Ring programming language version 1.8 book - Part 77 of 202
 
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, howTomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
 
Performance #1: Memory
Performance #1: MemoryPerformance #1: Memory
Performance #1: Memory
 
Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)
 

Andere mochten auch

Android - Thread, Handler and AsyncTask
Android - Thread, Handler and AsyncTaskAndroid - Thread, Handler and AsyncTask
Android - Thread, Handler and AsyncTask
Hoang Ngo
 
02 a xml_foundations
02 a xml_foundations02 a xml_foundations
02 a xml_foundations
Salma Zaki
 
Kepimpinan rasulullah di madinah
Kepimpinan rasulullah di madinahKepimpinan rasulullah di madinah
Kepimpinan rasulullah di madinah
Abdullah Kadir
 
Adab dengan rasulullah
Adab dengan rasulullahAdab dengan rasulullah
Adab dengan rasulullah
Abdullah Kadir
 
SFR Presentation
SFR PresentationSFR Presentation
SFR Presentation
sfrgm
 
Perspektif hindu terhadap fitrah kejadian manusia
Perspektif hindu terhadap fitrah kejadian manusiaPerspektif hindu terhadap fitrah kejadian manusia
Perspektif hindu terhadap fitrah kejadian manusia
Abdullah Kadir
 
Penubuhan sukan dan permainan sekolah rendah
Penubuhan sukan dan permainan sekolah rendahPenubuhan sukan dan permainan sekolah rendah
Penubuhan sukan dan permainan sekolah rendah
Abdullah Kadir
 

Andere mochten auch (19)

Android - Thread, Handler and AsyncTask
Android - Thread, Handler and AsyncTaskAndroid - Thread, Handler and AsyncTask
Android - Thread, Handler and AsyncTask
 
Efficient Android Threading
Efficient Android ThreadingEfficient Android Threading
Efficient Android Threading
 
02 a xml_foundations
02 a xml_foundations02 a xml_foundations
02 a xml_foundations
 
Kepimpinan rasulullah di madinah
Kepimpinan rasulullah di madinahKepimpinan rasulullah di madinah
Kepimpinan rasulullah di madinah
 
Adab dengan rasulullah
Adab dengan rasulullahAdab dengan rasulullah
Adab dengan rasulullah
 
Ppt nuqaba
Ppt nuqabaPpt nuqaba
Ppt nuqaba
 
Editorial
EditorialEditorial
Editorial
 
Hiren-CV
Hiren-CVHiren-CV
Hiren-CV
 
SFR Presentation
SFR PresentationSFR Presentation
SFR Presentation
 
Android Jam - Loaders - Udacity Lesson 4c
Android Jam - Loaders - Udacity Lesson 4cAndroid Jam - Loaders - Udacity Lesson 4c
Android Jam - Loaders - Udacity Lesson 4c
 
Perspektif hindu terhadap fitrah kejadian manusia
Perspektif hindu terhadap fitrah kejadian manusiaPerspektif hindu terhadap fitrah kejadian manusia
Perspektif hindu terhadap fitrah kejadian manusia
 
Peranan nuqaba
Peranan nuqabaPeranan nuqaba
Peranan nuqaba
 
Business analysis of namibia
Business analysis of namibiaBusiness analysis of namibia
Business analysis of namibia
 
Penubuhan sukan dan permainan sekolah rendah
Penubuhan sukan dan permainan sekolah rendahPenubuhan sukan dan permainan sekolah rendah
Penubuhan sukan dan permainan sekolah rendah
 
Inflation
InflationInflation
Inflation
 
Scheduling in Android
Scheduling in AndroidScheduling in Android
Scheduling in Android
 
Android Things Internals
Android Things InternalsAndroid Things Internals
Android Things Internals
 
Embedded Android Workshop with Nougat
Embedded Android Workshop with NougatEmbedded Android Workshop with Nougat
Embedded Android Workshop with Nougat
 
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental pluginMastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
 

Ähnlich wie Android Loaders : Reloaded

Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
Yekmer Simsek
 
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
Droidcon Berlin
 

Ähnlich wie Android Loaders : Reloaded (20)

Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
 
Common mistakes in android development
Common mistakes in android developmentCommon mistakes in android development
Common mistakes in android development
 
Core Java Programming Language (JSE) : Chapter XII - Threads
Core Java Programming Language (JSE) : Chapter XII -  ThreadsCore Java Programming Language (JSE) : Chapter XII -  Threads
Core Java Programming Language (JSE) : Chapter XII - Threads
 
droidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutinesdroidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutines
 
Modern c++
Modern c++Modern c++
Modern c++
 
Ob1k presentation at Java.IL
Ob1k presentation at Java.ILOb1k presentation at Java.IL
Ob1k presentation at Java.IL
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?
 
Performance #6 threading
Performance #6  threadingPerformance #6  threading
Performance #6 threading
 
Java design patterns
Java design patternsJava design patterns
Java design patterns
 
Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016
 
Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleConstruire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradle
 
Native Java with GraalVM
Native Java with GraalVMNative Java with GraalVM
Native Java with GraalVM
 
Java Concurrency and Asynchronous
Java Concurrency and AsynchronousJava Concurrency and Asynchronous
Java Concurrency and Asynchronous
 
Java Enterprise Edition
Java Enterprise EditionJava Enterprise Edition
Java Enterprise Edition
 
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdfITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
 
Introduction to Griffon
Introduction to GriffonIntroduction to Griffon
Introduction to Griffon
 
Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15
 
Android App Development - 07 Threading
Android App Development - 07 ThreadingAndroid App Development - 07 Threading
Android App Development - 07 Threading
 
Eric Lafortune - ProGuard: Optimizer and obfuscator in the Android SDK
Eric Lafortune - ProGuard: Optimizer and obfuscator in the Android SDKEric Lafortune - ProGuard: Optimizer and obfuscator in the Android SDK
Eric Lafortune - ProGuard: Optimizer and obfuscator in the Android SDK
 
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
Droidcon2013 pro guard, optimizer and obfuscator in the android sdk_eric lafo...
 

Kürzlich hochgeladen

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Kürzlich hochgeladen (20)

Top 10 Most Downloaded Games on Play Store in 2024
Top 10 Most Downloaded Games on Play Store in 2024Top 10 Most Downloaded Games on Play Store in 2024
Top 10 Most Downloaded Games on Play Store in 2024
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Manulife - Insurer Innovation Award 2024
Manulife - Insurer Innovation Award 2024Manulife - Insurer Innovation Award 2024
Manulife - Insurer Innovation Award 2024
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 

Android Loaders : Reloaded

  • 1. Android Loaders R E L O A D E D Christophe Beyls Brussels GTUG 13th march 2013 READY. LOAD "*",8,1 SEARCHING FOR * LOADING READY. RUN▀
  • 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 every day at work. @BladeCoder
  • 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. A bit of History 1. Plain Threads final Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch(msg.what) { case RESULT_WHAT: handleResult((Result) msg.obj); return true; } return false; } }); Thread thread = new Thread(new Runnable() { @Override public void run() { Result result = doStuff(); if (isResumed()) { handler.sendMessage(handler.obtainMessage(RESULT_WHAT, result)); } } }); thread.start();
  • 6. A bit of History 1. Plain Threads Difficulties: ● Requires you to post the result back on the main thread; ● Cancellation must be handled manually; ● Want a thread pool? You need to implement it yourself.
  • 7. A bit of History 2. AsyncTask (Android's SwingWorker) ● Handles thread switching for you : result is posted to the main thread. ● Manages scheduling for you. ● Handles cancellation: if you call cancel(), onPostExecute() will not be called. ● Allows to report progress.
  • 8. A bit of History 2. AsyncTask private class DownloadFilesTask extends AsyncTask<Void, Integer, Result> { @Override protected void onPreExecute() { // Something like showing a progress bar } @Override protected Result doInBackground(Void... params) { Result result = new Result(); for (int i = 0; i < STEPS; i++) { result.add(doStuff()); publishProgress(100 * i / STEPS); } return result; } @Override protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } @Override protected void onPostExecute(Result result) { handleResult(result); } }
  • 9. A bit of History 2. AsyncTask Problems: ● You need to keep a reference to each running AsyncTask to be able to cancel it when your Activity is destroyed. ● Memory leaks: as long as the AsyncTask runs, it keeps a reference to its enclosing Activity even if the Activity has already been destroyed. ● Results arriving after the Activity has been recreated (orientation change) are lost.
  • 10. A bit of History 2. AsyncTask A less known but big problem. Demo
  • 11. A bit of History 2. AsyncTask AsyncTask 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! They run in sequence, unless you execute them with executeOnExecutor() with a ThreadPoolExecutor. → No parallelization by default on modern phones.
  • 12. A bit of History 2. AsyncTask A 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. Loaders to the rescue ● Allows an Activity or Fragment to reconnect to the same Loader after recreation and retrieve the last result. ● If the result comes after a Loader has been disconnected from an Activity/Fragment, it can keep it in cache to deliver it when reconnected to the recreated Activity/Fragment. ● A Loader monitors its data source and delivers new results when the content changes. ● Loaders handle allocation/disallocation of resources associated with the result (example: Cursors).
  • 14. Loaders to the rescue If you need to perform any kind of asynchronous load in an Activity or Fragment, you must never use AsyncTask again. And don't do like this man because Loaders are much more than just CursorLoaders.
  • 15. Using the LoaderManager ● Simple API to allow Activities and Fragments to interact with Loaders. ● One instance of LoaderManager for each Activity and each Fragment. They don't 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. Using the LoaderManager private final LoaderCallbacks<Result> loaderCallbacks = new LoaderCallbacks<Result>() { @Override public Loader<Result> onCreateLoader(int id, Bundle args) { return new MyLoader(getActivity(), args.getLong("id")); } @Override public void onLoadFinished(Loader<Result> loader, Result result) { handleResult(result); } @Override public void onLoaderReset(Loader<Result> loader) { } }; Never call a standard Loader method yourself directly on the Loader. Always use the LoaderManager.
  • 17. Using the LoaderManager When to init Loaders at Activity/Fragment startup Activities @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... getSupportLoaderManager().initLoader(LOADER_ID, null, callbacks); } Fragments @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ... getLoaderManager().initLoader(LOADER_ID, null, callbacks); }
  • 18. Loaders lifecycle A loader has 3 states: ● Started ● Stopped ● Reset The LoaderManager automatically changes the state of the Loaders according to the Activity or Fragment state.
  • 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 the results and keep them in a local cache. ● Activity/Fragment is destroyed or restartLoader() is called or destroyLoader() is called → Loader resets: onReset()
  • 20. Passing arguments Using args Bundle private void onNewQuery(String query) { Bundle args = new Bundle(); args.putString("query", query); getLoaderManager().restartLoader(LOADER_ID, args, loaderCallbacks); } @Override public Loader<Result> onCreateLoader(int id, Bundle args) { return new QueryLoader(getActivity(), args.getString("query")); }
  • 21. Passing arguments Using args Bundle ● You don't 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 instance variables too. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.newsId = getArguments().getLong("newsId"); } ... @Override public Loader<News> onCreateLoader(int id, Bundle args) { return new NewsLoader(getActivity(), NewsFragment.this.newsId); }
  • 22. A LoaderManager bug When a Fragment is recreated on configuration change, its LoaderManager calls the onLoadFinished() callback twice to send back the last result of the Loader when you call initLoader() in onActivityCreated(). 3 possible workarounds: 1. Don't do anything. If your code permits it. 2. Save the previous result and check if it's different. 3. Call setRetainInstance(true) in onCreate().
  • 23. One-shot Loaders Sometimes you only want to perform a loader action once. Example: submitting a form. ● You call initLoader() in response to an action. ● You need to reconnect to the loader on orientation change to get the result.
  • 24. One-shot Loaders In your LoaderCallbacks @Override public void onLoadFinished(Loader<Integer> loader, Result result) { getLoaderManager().destroyLoader(LOADER_ID); ... // Process the result } On Activity/Fragment creation @Override public void onActivityCreated(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Reconnect to the loader only if present if (getLoaderManager().getLoader(LOADER_ID) != null) { getLoaderManager().initLoader(LOADER_ID, null, this); } }
  • 25. Common mistakes 1. Don't go crazy with loader ids You don't need to: ● Increment the loader id or choose a random loader id each time you initialize or restart a Loader. This will prevent your Loaders from being reused and will create a complete mess! Use a single unique id for each kind of Loader.
  • 26. Common mistakes 1. Don't go crazy with loader ids You don't need to: ● Create a loader id constant for each and every kind of Loader accross your entire app. Each LoaderManager is independent. Just create private constants in your Activity or Fragment for each kind of loader in it.
  • 27. Common mistakes 2. Avoid FragmentManager Exceptions You can not create a FragmentTransaction directly in LoaderCallbacks. This includes any dialog you create as a DialogFragment. Solution: Use a Handler to dispatch the FragmentTransaction.
  • 28. Common mistakes 2. Avoid FragmentManager Exceptions public class LinesFragment extends ContextMenuSherlockListFragment implements LoaderCallbacks<List<LineInfo>>, Callback { ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new Handler(this); adapter = new LinesAdapter(getActivity()); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(adapter); setListShown(false); getLoaderManager().initLoader(LINES_LOADER_ID, null, this); }
  • 29. Common mistakes 2. Avoid FragmentManager Exceptions @Override public Loader<List<LineInfo>> onCreateLoader(int id, Bundle args) { return new LinesLoader(getActivity()); } @Override public 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. Common mistakes 2. Avoid FragmentManager Exceptions @Override public void onLoaderReset(Loader<List<LineInfo>> loader) { adapter.setLinesList(null); } @Override public 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. Implementing a basic Loader 3 classes provided by the support library: ● Loader Base abstract class. ● AsyncTaskLoader Abstract class, extends Loader. ● CursorLoader Extends AsyncTaskLoader. Particular implementation dedicated to querying ContentProviders.
  • 32. AsyncTaskLoader Does it suffer from AsyncTask's limitations?
  • 33. AsyncTaskLoader Does it suffer from AsyncTask's limitations? No, because it uses ModernAsyncTask internally, which has the same implementation on 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. Implementing a basic Loader IMPORTANT: Avoid memory leaks By design, Loaders only keep a reference to the Application context so there is no leak: /** * Stores away the application context associated with context. Since Loaders can be * used across multiple activities it's 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 be declared static or they will keep an implicit reference to their parent!
  • 35. Implementing a basic Loader We need to extend AsyncTaskLoader and implement its behavior.
  • 36. Implementing a basic Loader The callbacks to implement Mandatory ● onStartLoading() ● onStopLoading() ● onReset() ● onForceLoad() from Loader OR loadInBackground() from AsyncTaskLoader Optional ● deliverResult() [override]
  • 37. Implementing a basic Loader public abstract class BasicLoader<T> extends AsyncTaskLoader<T> { public BasicLoader(Context context) { super(context); } @Override protected void onStartLoading() { forceLoad(); // Launch the background task } @Override protected void onStopLoading() { cancelLoad(); // Attempt to cancel the current load task if possible } @Override protected void onReset() { super.onReset(); onStopLoading(); } }
  • 38. Implementing a basic Loader public abstract class LocalCacheLoader<T> extends AsyncTaskLoader<T> { private T mResult; public AbstractAsyncTaskLoader(Context context) { super(context); } @Override protected 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. Implementing a basic Loader @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } @Override protected void onReset() { super.onReset(); onStopLoading(); mResult = null; } @Override public void deliverResult(T data) { mResult = data; if (isStarted()) { // If the Loader is currently started, we can immediately // deliver its results. super.deliverResult(data); } } }
  • 40. Implementing a basic Loader What about a global cache instead? public abstract class GlobalCacheLoader<T> extends AsyncTaskLoader<T> { ... @Override protected 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. Monitoring data Two 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. @Override protected void onStartLoading() { if (mResult != null) { deliverResult(mResult); } if (takeContentChanged() || mResult == null) { forceLoad(); } }
  • 42. AutoRefreshLoader public abstract class AutoRefreshLoader<T> extends LocalCacheLoader<T> { private long interval; private Handler handler; private final Runnable timeoutRunnable = new Runnable() { @Override public void run() { onContentChanged(); } }; public AutoRefreshLoader(Context context, long interval) { super(context); this.interval = interval; this.handler = new Handler(); } ...
  • 43. AutoRefreshLoader ... @Override protected void onForceLoad() { super.onForceLoad(); handler.removeCallbacks(timeoutRunnable); handler.postDelayed(timeoutRunnable, interval); } @Override public void onCanceled(T data) { super.onCanceled(data); // Retry a refresh the next time the loader is started onContentChanged(); } @Override protected void onReset() { super.onReset(); handler.removeCallbacks(timeoutRunnable); } }
  • 44. CursorLoader CursorLoader is a Loader dedicated to querying ContentProviders ● It returns a database Cursor as result. ● It performs the database query on a background thread (it inherits from AsyncTaskLoader). ● It replaces Activity.startManagingCursor(Cursor c) It manages the Cursor lifecycle according to the Activity Lifecycle. → Never call close() ● It monitors the database and returns a new cursor when data has changed. → Never call requery()
  • 45. CursorLoader Usage with a CursorAdapter in a ListFragment @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new BookmarksLoader(getActivity(), args.getDouble("latitude"), args.getDouble("longitude")); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { adapter.swapCursor(data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); }
  • 47. SimpleCursorLoader If you don't need the complexity of a ContentProvider ... but want to access a local database anyway ● SimpleCursorLoader is an abstract class based on CursorLoader with all the ContentProvider-specific stuff removed. ● You just need to override one method which performs the actual database query.
  • 48. SimpleCursorLoader Example usage - bookmarks private 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; } @Override protected Cursor getCursor() { return DatabaseManager.getInstance().getBookmarks(latitude, longitude); } }
  • 49. SimpleCursorLoader Example usage - bookmarks public class DatabaseManager { private static final Uri URI_BOOKMARKS = Uri.parse("sqlite://your.package.name/bookmarks"); ... public Cursor getBookmarks(double latitude, double longitude) { // A big database query you don't want to see ... cursor.setNotificationUri(context.getContentResolver(), URI_BOOKMARKS); return cursor; } ...
  • 50. SimpleCursorLoader Example usage - bookmarks ... public boolean addBookmark(Bookmark bookmark) { SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransaction(); try { // Other database stuff you don't want to see ... long result = db.insert(DatabaseHelper.BOOKMARKS_TABLE_NAME, null, values); db.setTransactionSuccessful(); // Will return -1 if the bookmark was already present return result != -1L; } finally { db.endTransaction(); context.getContentResolver().notifyChange(URI_BOOKMARKS, null); } } }
  • 52. Loaders limitations 1. No built-in progress updates support Workaround: use LocalBroadcastManager. In the Activity: @Override protected void onStart() { // Receive loading status broadcasts in order to update the progress bar LocalBroadcastManager.getInstance(this).registerReceiver(loadingStatusReceiver, new IntentFilter(MyLoader.LOADING_ACTION)); super.onStart(); } @Override protected void onStop() { super.onStop(); LocalBroadcastManager.getInstance(this) .unregisterReceiver(loadingStatusReceiver); }
  • 53. Loaders limitations 1. No built-in progress updates support Workaround: use LocalBroadcastManager. In the Loader: @Override public Result loadInBackground() { // Show progress bar Intent intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, true); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); try { return doStuff(); } finally { // Hide progress bar intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, false); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } }
  • 54. Loaders limitations 2. 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 a composite Object like Pair<T, Exception>. Warning: your Loader's cache must be smarter and check if the object is null or contains an error. 2) Add a property to your Loader to expose a catched Exception.
  • 55. Loaders limitations public abstract class ExceptionSupportLoader<T> extends LocalCacheLoader<T> { private Exception lastException; public ExceptionSupportLoader(Context context) { super(context); } public Exception getLastException() { return lastException; } @Override public T loadInBackground() { try { return tryLoadInBackground(); } catch (Exception e) { this.lastException = e; return null; } } protected abstract T tryLoadInBackground() throws Exception; }
  • 56. Loaders limitations 2. No error handling in LoaderCallbacks. Workaround #2 (end) Then in your LoaderCallbacks: @Override public void onLoadFinished(Loader<Result> loader, Result result) { if (result == null) { Exception exception = ((ExceptionSupportLoader<Result>) loader) .getLastException(); // Error handling } else { // Result handling } }
  • 57. The End We made it! Thank you for watching. Questions?