1 of 54

Deep dive

into Android async operations

Mateusz Grzechociński

@mgrzechocinski

2 of 54

What’s it for?

  • Confitura 2010
  • Review & share own experience
  • ...and would appreciate your’s
  • From developer to (intermediate) developers

3 of 54

Meet Adam

4 of 54

...and his story

5 of 54

Adam, a developer who knows

  • what’s Looper.loop()
  • what ANR is
  • Long operation? Spawn a thread!

6 of 54

Requirements

  • Bundesliga matches results
  • Refresh on click

7 of 54

Simple enough?

  • REST client
  • 1 activity
  • 1 button,
  • refresh = call URLs in spawned threads

8 of 54

StrictMode

public class DemoApp extends Application {� @Overridepublic void onCreate() {� super.onCreate();� StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()� .detectDiskReads()� .detectDiskWrites()� .detectNetwork()� .penaltyLog()� .build());� }�}

9 of 54

"AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.”

Android SDK docs

10 of 54

11 of 54

DEMO 1

AsyncTask - multiple calls

12 of 54

< DONUT

DONUT

>=HONEYCOMB

SERIAL

PARALLEL

SERIAL

AsyncTask.executeOnExecutor(Executor)

13 of 54

DEMO 1a

AsyncTask - memory leak

14 of 54

15 of 54

Solutions

  • Track activity/fragment lifecycle manually
    • cancel it?
  • Use fragment with setRetainInstance(true)

16 of 54

AsyncTask

Pros

  • Simple
  • Great for few seconds operations
  • Cancellation support

Cons

  • Memory leaks,
  • Possible crashes
  • Behaves different
  • Lack of exceptions handling (or RoboAsyncTask)

17 of 54

Adam wonders: Really a good idea?

run task

→ change config (e.g. by rotating the screen)

→ cancel previous task

run new task

→ update UI

18 of 54

Loaders

  • HONEYCOMB+
  • support-v4

19 of 54

Loaders, in particular CursorLoader, are expected to retain their data after being stopped. This allows applications to keep their data across the activity or fragment's onStop() and onStart() methods, so that when users return to an application, they don't have to wait for the data to reload.

Android SDK docs

20 of 54

AsyncTaskLoader

“This class performs the same function as the AsyncTask, but a bit better. It can handle Activity configuration changes more easily (add MG: by Loader Manager), and it behaves within the life cycles of Fragments and Activities.”

http://www.javacodegeeks.com/2013/01/android-loaders-versus-asynctask.html

21 of 54

How it works...

  • LoaderManager
  • initLoader
  • LoaderCallback
  • CursorLoader
  • AsyncTaskLoader

22 of 54

DEMO 2

Loader - first shoot and…

nothing

WHY?

23 of 54

24 of 54

@Overrideprotected void onStartLoading() {� super.onStartLoading();

Log.d(LOG_TAG, "onStartLoading");

+ forceLoad();�}

25 of 54

DEMO 3

Loader - forceLoad()

config change?

26 of 54

@Overrideprotected void onCreate(Bundle savedInstanceState) {� super.onCreate(savedInstanceState);� setContentView(R.layout.activity_matches);� if (savedInstanceState != null) {�+ getSupportLoaderManager().initLoader(LOADER_ID, args, this);

//restore state...� }�}

27 of 54

DEMO 4

Loader - config change

leaving activity?

28 of 54

@Overrideprotected void onStartLoading() {� super.onStartLoading();

Log.d(LOG_TAG, "onStartLoading");�+ if(result == null){� forceLoad();�+ }else{�+ deliverResult(result);�+ }�}

29 of 54

DEMO 5

Loader - leaving activity (home etc)

force refresh?

30 of 54

@Overridepublic void onLoadFinished(Loader<Match> loader, Match match) {� Log.d(LOG_TAG, "onLoadFinished: " + match);� currentMatches.add(match);� adapter.setCurrentMatches(currentMatches);� adapter.notifyDataSetChanged(); �+ getSupportLoaderManager().destroyLoader(LOADER_ID);�}

31 of 54

DEMO 6

Loader - force refresh

32 of 54

Loader

Pros

  • Better than AsyncTask, much
  • Gives “partial” caching
    • Need in-loader result caching when leaving activity
    • No control
  • No memory leaks

Cons

  • Designed to support DB data (cursor)
  • Hacking, hacking, hacking to use in networking
  • Poorly documented
  • Opposite to AsyncTask
    • too highly coupled
  • Poor exception management

33 of 54

34 of 54

The idea

“[...]

Basically, what happens with RS is that when a request is being processed, its listeners will be invoked as long as the associated activity is alive.

[...]

The main purpose of RS is to make sure that there is no memory leak : your activity, if it has to die, will die and be garbage collected, RS doesn't hold any hard reference to it that would prevent garbage collection. That's really the core idea behind RoboSpice. [...]”

Stephen Nicolas

http://stackoverflow.com/questions/19011200/how-does-robospice-manage-activity-lifecycle

35 of 54

36 of 54

Show me do code!

37 of 54

DEMO 7

RoboSpice - basic

  • config change
  • refresh() → no caching

38 of 54

Config change

@Overrideprotected void onStart() {� super.onStart();� spiceManager.start(this);�+ spiceManager.addListenerIfPending(Match.class, CACHE_KEY, this);�}

39 of 54

Force refresh

private void loadMatchesOnClick() {� int matchID = 1;� MatchResultRequest request = new MatchResultRequest(Match.class, matchID);� spiceManager.execute(request, CACHE_KEY, DurationInMillis.ALWAYS_EXPIRED, this);�}

or

spiceManager.removeDataFromCache(Match.class, CACHE_KEY);

40 of 54

DEMO 8

RoboSpice - extended

41 of 54

By default

  • Handling config changes
  • Full control caching
  • Multithreading
  • No internet connection → NoNetworkException.class
  • Modules to SpringAndroid, Retrofit
  • and much more...

42 of 54

RoboSpice

Pros

  • Robust, full featured
  • Last commit < 3 months ago

Cons

  • POJO per Request
  • No a simple library
    • SpiceManager with 1300 LOC

43 of 54

Go Reactive

  • ReactiveExtentions by MS
  • Ported by Netflix
  • rxAndroid = rxJava + few Android classes
  • rxJava hit 1.0
  • rxAndroid still 0.x

44 of 54

45 of 54

Motivations

"[...] If a calling Activity/Fragment makes multiple requests, it needs to handle responses in a single function by switching on the URL. This makes for code that isn’t very readable, especially when you have to jump from where the request is executed to where the response is handled"

http://markhudnall.com/2013/10/15/rxjava-and-android/

46 of 54

Motivations

“It’s still a little verbose, but it’s more similar to writing synchronous code. I use the response in code that directly follows the request. I also get error handling on the UI thread for free.”

http://markhudnall.com/2013/10/15/rxjava-and-android/

47 of 54

Iterable

Observable

getDataFromLocalMemory()� .skip(10)� .take(5)� .map({ s -> return s + " transformed" })� .forEach({ println "next => " + it })

getDataFromNetwork()� .skip(10)� .take(5)� .map({ s -> return s + " transformed" })� .subscribe({ println "onNext => " + it })

48 of 54

getDataFromNetwork()� .skip(10)� .take(5)� .map({ s -> return s + " transformed" })� .subscribe({ println "onNext => " + it })

49 of 54

DEMO 9

RxAndroid in UI Layer

50 of 54

Worth trying?

  • Streams of events
    • e.g. Weather station, tweets analyzer
  • In “common” app?
  • When using Retrofit

@GET("/user/{id}/photo")Observable<Photo> getUserPhoto(@Path("id") int id);

51 of 54

rxAndroid

Pros

  • Reduced callback hell with FPP
  • Robust set of functions/transformations

Cons

  • Need to change the way of thinking
  • Hard, really
  • On Android - even harder
  • Possible memory leaks?
  • Debugging?

52 of 54

Out of scope… or time

  • IntentService (!= Service)
  • Event buses (Otto, GreenRobot’s EventBus)
    • especially with Loaders
  • Volley
  • Ion

53 of 54

Remember

  • Don’t use AsyncTask
  • Consider using Loaders
  • Give a try RoboSpice
  • Learn FRP (rxJava/rxAndroid)

54 of 54

Thanks

Mateusz Grzechociński

@mgrzechocinski

http://grzechocinski.net