Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Multi-Threading, Concurrency and Async on Android - Mobile Era 2016

2307a37297162f815342545a2068b2f1?s=47 Erik Hellman
November 03, 2016

Multi-Threading, Concurrency and Async on Android - Mobile Era 2016

Everything you wanted to know about multi-threading, concurrency and asynchronous operations on Android but was to afraid to ask.

2307a37297162f815342545a2068b2f1?s=128

Erik Hellman

November 03, 2016
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. Multi-threading, concurrency and async on Android Erik Hellman, Hellsoft @ErikHellman

    - www.hellsoft.se - erik.hellman@hellsoft.se 1
  2. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 2

  3. Multi-tasking is really hard! @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 3

  4. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 4

  5. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 5

  6. RKFBIIRSCBSUSSR @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 6

  7. RK FBI IRS CBS USSR @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se

    7
  8. Dining Philisopher's Dilemma @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 8

  9. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 9

  10. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 10

  11. What if we wrote Android apps like this? setContentView(R.layout.main_activity); setState(State.RESUMED);

    while (getState().equals(State.RESUMED)) { TextView v = (TextView) findViewById(R.id.text); Button b = (Button) findViewById(R.id.btn); if(v.isClicked()) { showKeyboard(v); } else if(isBackPressed() || isHomePressed()) { setState(State.PAUSED); } else if(b.isClicked()) { String text = v.getText().toString(); submitTextToServer(text); } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 11
  12. Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(SourceFile:1493) at android.support.v4.app.FragmentManagerImpl.enqueueAction(SourceFile:1511) at android.support.v4.app.BackStackRecord.commitInternal(SourceFile:638) at android.support.v4.app.BackStackRecord.commit(SourceFile:617) at com.myapp.MainActivity.navigateToFragment(SourceFile:519) at com.myapp.MainActivity.onNavigationDrawerItemSelected(SourceFile:360) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:168) at android.app.ActivityThread.main(ActivityThread.java:5885) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687) @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 12
  13. Threads on Android & Linux @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se

    13
  14. App processes on Android @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 14

  15. Fork a new process #include <unistd.h> void start_new_app() { pid_t

    new_pid = fork(); if(new_pid == 0) { // Child process init goes here } else { // Parent process registers new child here } } // fork() is implemented in the kernel using clone() clone(SIGCHLD, 0); @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 15
  16. Start a new thread (Java) public void startNewThread() { Thread

    newThread = new Thread(new Runnable() { public void run() { // Do my thing! } }); newThread.start() } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 16
  17. Start a new thread (C) #include <pthread.h> void *call_from_thread(void *)

    { // do stuff return NULL; } JNIEXPORT void JNICALL Java_se_hellsoft_async_Native_start(JNIEnv * env, jobject obj) { pthread_t t; //Launch a thread pthread_create(&t, NULL, call_from_thread, NULL); } // Start a new thread in the Linux kernel clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0); @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 17
  18. Multi-threading problem public class Product { public long id; public

    String title; public long price; } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 18
  19. Synchronization - it's important! If multiple threads access the same

    mutable state variable without proper synchronization, your program is broken. -- Java Concurrency in Practice @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 19
  20. Fixing it! • Don't share • Make it immutable •

    Use synchronization @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 20
  21. Reentrant // If not reentrant - this would cause a

    deadlock! public class ReentrantDemo { private String value; public synchronized void set(String value) { this.value = value; } public synchronized String get() { return this.value; } public synchronized String getAndSet(String value) { String tmp = this.get(); this.set(value); return tmp; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 21
  22. Reordering There is no guarantee that the operations in one

    thread will be performed in the order given by the program, as long as the reordering is not detectable from within that thread - even if the reordering is apparent to other threads. — Java Concurrency in Practice @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 22
  23. Reordering and Visibility example public class ReorderingExample { private static

    boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while(!ready) Thread.yield(); System.out.println(number); } } // The order of execution here is not guaranteed! public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 23
  24. Stale data public class MutableLong { private long value; public

    long get() { return value; } public void set(long value) { this.value = value; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 24
  25. 64-bit non-atomic operations Non-volatile 64-bit variables can be read as

    two 32-bit operations -- Java Concurrency in Practice @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 25
  26. False immutability (1) public class FalseImmutability { private final String[]

    data = new String[] {"one", "two", "three"}; public String[] get() { return data; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 26
  27. False immutability (2) // Unmodifiable list is not the same

    as immutable! public class FalseImmutability { private final List<Data> listOfData; public FalseImmutability() { List<Data> tmp = Arrays.asList(new Data("one"), new Data("two"), new Data("three")); this.listOfData = Collections.unmodifiableList(tmp); } public List<String> get() { return listOfData; } public static class Data { private String name; public Data(String name) { this.name = name; } public void set(String name) { this.name = name; } public String get() { return name; } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 27
  28. Immutability with AutoValue (1) public class Money { public Currency

    currency; public long amount; } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 28
  29. Immutability with AutoValue (2) public final class Money { private

    Currency currency; private long amount; public Money(Currency currency, long amount) { this.currency = currency; this.amount = amount; } public Currency currency() { return currency; } public long amount() { return amount; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 29
  30. Immutability with AutoValue (3) public final class Money { private

    Currency currency; private long amount; public Money(Currency currency, long amount) { this.currency = currency; this.amount = amount; } public Currency currency() { return currency; } public long amount() { return amount; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Money money = (Money) o; if (Double.compare(money.amount, amount) != 0) return false; return currency.equals(money.currency); } @Override public int hashCode() { int result; long temp; result = currency.hashCode(); result = 31 * result + (int) (amount ^ (amount >>> 32)); return result; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 30
  31. Immutability with AutoValue (4) public final class Money { private

    Currency currency; private long amount; public Money(Currency currency, long amount) { this.currency = currency; this.amount = amount; } public Currency currency() { return currency; } public long amount() { return amount; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Money money = (Money) o; if (money.amount != amount) return false; return currency.equals(money.currency); } @Override public int hashCode() { int result; long temp; result = currency.hashCode(); result = 31 * result + (int) (amount ^ (amount >>> 32)); return result; } @Override public String toString() { return "Money{" + "currency=" + currency + ", amount=" + amount + '}'; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 31
  32. Immutability with AutoValue (5) @AutoValue public abstract class Money {

    public abstract Currency currency(); public abstract long amount(); } // Add to your build.gradle dependencies { apt 'com.google.auto.value:auto-value:1.4-rc1' } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 32
  33. AutoValue with Ryan Harter • http://ryanharter.com/blog/2016/03/22/ autovalue/ • https://www.youtube.com/watch?v=IPlDL4EsY08 @ErikHellman

    - www.hellsoft.se - erik.hellman@hellsoft.se 33
  34. Locking (1) public class ConsumerProducer { private String value; public

    void produce(String value) { this.value = value; } public String consume() { String consumed = this.value; this.value = null; return consumed; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 34
  35. Locking (2) public class ConsumerProducer { private String value; public

    synchronized void produce(String value) { this.value = value; } public synchronized String consume() { String consumed = this.value; this.value = null; return consumed; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 35
  36. Locking (3) public class ConsumerProducer { private String value; private

    final Object lock = new Object(); public void produce(String value) { synchronized(this.lock) { while(this.value != null) { this.lock.wait(); } this.value = value; this.lock.notifyAll(); } } public String consume() { synchronized(this.lock) { while(this.value == null) { this.lock.wait(); } String consumed = this.value; this.value = null; this.lock.notifyAll(); return consumed; } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 36
  37. Message Queue public class MessageQueue implements Runnable { private LinkedBlockingQueue<Message>

    queue; private Callback callback; public MessageQueue(Callback callback) { this.callback = callback; // From java.util.concurrent queue = new LinkedBlockingQueue<>(); // Process messages on bg thread new Thread(this).start(); } public void post(Message message) { queue.add(message); } @Override public void run() { try { while(true) { Message message = queue.take(); // Will block when no more messages callback.handleMessage(message); // How do we stop? } } catch (InterruptedException e) { // silently ignore for now... } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 37
  38. Don't re-invent the wheel! Handler.Callback callback = new Handler.Callback() {

    public boolean handleMessage(Message msg) { switch(msg.what) { case MY_CODE: doExpensiveWork(); break; } } } HandlerThread thread = new HandlerThread("my-processor"); thread.start(); Handler handler = new Handler(thread.getLooper(), callback); ... handler.obtainMessage(MY_CODE).sendToTarget(); @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 38
  39. Async - Happy path! @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 39

  40. Async - Crash! @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 40

  41. Stop a thread? public void startNewThread() { Thread myThread =

    new Thread() { public void run() { try (InputStream stream = new FileInputStream("/dev/urandom")) { byte[] buffer = new byte[512]; while (stream.read(buffer) != -1) { Log.d(TAG, "Read some data!"); } } catch (IOException e) { /* ignore for now */ } } }; myThread.start(); SystemClock.sleep(1000); myThread.interrupt(); } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 41
  42. @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 42

  43. Thread.interrupt() An interrupt is an indication to a thread that

    it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate. — The Java™ Tutorials @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 43
  44. Check for interrupted() while (stream.read(buffer) != -1) { Log.d(TAG, "Read

    some data!"); // Check after each read if we're interrupted if (interrupted()) { Log.d(TAG, "Got interrupted - cancel reading!"); return; } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 44
  45. Cancel/Unregister/Unsubscribe 1.Set a flag 2.Remove listener/callback/subscriber 3.return from run() (optional)

    @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 45
  46. Multi-threaded async challenge (1) // How do we cancel result

    delivery? public class MyWorker extends Thread { private Callback callback; public MyWorker(Callback callback) { this.callback = callback; } public void run() { List<Data> result = reallyExpensiveOperation(); this.callback.onResult(result); } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 46
  47. Multi-threaded async challenge (2) // Oh-oh.. no synchronization! public class

    MyWorker extends Thread { private Callback callback; public MyWorker(Callback callback) { this.callback = callback; } public void cancel() { this.callback = null; } public void run() { List<Data> result = reallyExpensiveOperation(); if(this.callback != null) this.callback.onResult(result); } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 47
  48. Multi-threaded async challenge (3) // My callbacks end up on

    the wrong thread! public class MyWorker extends Thread { private Callback callback; public MyWorker(Callback callback) { this.callback = callback; } public synchronized void cancel() { this.callback = null; } public void run() { List<Data> result = reallyExpensiveOperation(); synchronized(this) { if(this.callback != null) this.callback.onResult(result); } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 48
  49. Multi-threaded async challenge (4) // Everything is awesome! public class

    MyWorker implements Handler.Callback { private Handler callback; private Handler worker; private boolean cancelled = false; public MyWorker(Handler callback) { this.callback = callback; HandlerThread t = new HandlerThread("myWorker"); t.start(); worker = new Handler(t.getLooper(), this); worker.sendEmptyMessage(1); } public synchronized void cancel() { cancelled = true; callback.removeMessages(RESULT_CODE); } public booleand handleMessage(Message msg) { List<Data> result = reallyExpensiveOperation(); synchronized(this) { if(!cancelled) { callback.obtainMessage(RESULT_CODE, result) .sendToTarget(); } } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 49
  50. Don't re-invent the wheel! @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 50

  51. How to use AsyncTask (1) protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task_sample); myTask = new AsyncTask<Void, Void, String>() { @Override protected void onPreExecute() { super.onPreExecute(); ((TextView) findViewById(R.id.result)).setText("Started!"); } @Override protected String doInBackground(Void... params) { doSomeHeavyWork(); if(isCancelled()) return null; doSomeFinalWork(); if(isCancelled()) return null; return "Result!"; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); ((TextView) findViewById(R.id.result)).setText(result); } }; myTask.execute(); } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 51
  52. How to use AsyncTask (2) protected void onDestroy() { super.onDestroy();

    // Very, very, very important! myTask.cancel(false); } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 52
  53. Don't do this private class MyAsynkTask extends AsyncTask<Void, Void, String>

    { // methods omitted for brevity... @Override protected void onCancelled() { super.onCancelled(); // Don't touch the view in onCancelled!!!! ((TextView) findViewById(R.id.result)).setText("Cancelled!"); } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 53
  54. Bonus - AsyncTask with progress public class AsyncTaskWithProgress extends AsyncTask<String,

    AsyncTaskWithProgress.State, String> { protected String doInBackground(String... params) { try { publishProgress(State.Started); SystemClock.sleep(1000); publishProgress(State.Loading); SystemClock.sleep(1000); publishProgress(State.Updating); SystemClock.sleep(1000); publishProgress(State.Completed); return "Done!"; } catch (Exception e) { publishProgress(State.Failed); return "Failed!"; } } public enum State { Started, Loading, Updating, Completed, Cancelled, Failed } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 54
  55. Loaders (1) public class LoaderSampleActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<Data>> {

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_loader_sample); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<List<Data>> onCreateLoader(int id, Bundle args) { return new DataListLoader(getApplicationContext()); } public void onLoadFinished(Loader<List<Data>> loader, List<Data> data) { // Hand data to your adapter... } public void onLoaderReset(Loader<List<Data>> loader) { } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 55
  56. Loaders (2) public static class DataListLoader extends AsyncTaskLoader<List<Data>> { private

    List<Data> loadedData; public DataListLoader(Context context) { super(context); } protected void onStartLoading() { super.onStartLoading(); if (loadedData == null) { forceLoad(); } else { deliverResult(loadedData); } } public List<Data> loadInBackground() { // Load the data... return newData; } public void deliverResult(List<Data> data) { loadedData = data; super.deliverResult(data); } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 56
  57. IntentService public class IntentServiceSample extends IntentService { public IntentServiceSample() {

    super("name"); } @Override protected void onHandleIntent(Intent intent) { try { ProductResource productResource = RetrofitHelper.getProductResource(); // Synchronous call since we're on a background thread Response<List<Product>> response = productResource.getProducts().execute(); List<Product> products = response.body(); writeListToDataBase(products); } catch (IOException e) { // Error handling } } private void writeListToDataBase(List<Product> products) { // Insert into database, synchronously... } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 57
  58. RxJava public class RxJavaSample extends Activity { private MyAdapter adapter;

    private Subscription subscription; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); adapter = new MyAdapter(); Observable<List<Data>> listOfData = DataLoader.loadData(); // Load on IO scheduler subscription = listOfData.subscribeOn(io()).observeOn(mainThread()) .subscribe(data -> { adapter.setData(data); adapter.notifyDataSetChanged(); }); } protected void onDestroy() { super.onDestroy(); // Really, really important!!! if(!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 58
  59. Timeouts with Handler (1) uiHandler = new Handler(Looper.getMainLooper(), this); swipeRefreshLayout

    = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); swipeRefreshLayout.setOnRefreshListener(this::performNetworkSync); @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 59
  60. Timeouts with Handler (2) private void performNetworkSync() { swipeRefreshLayout.setRefreshing(true); uiHandler.sendEmptyMessageDelayed(MSG_TIMEOUT,

    REFRESH_TIMEOUT); call = RetrofitHelper.getProductResource().getProducts(); call.enqueue(new Callback<List<Product>>() { @Override public void onResponse(Call<List<Product>> call, Response<List<Product>> response) { uiHandler.obtainMessage(MSG_PRODUCTS_UPDATED, response.body()).sendToTarget(); uiHandler.removeMessages(MSG_TIMEOUT); } @Override public void onFailure(Call<List<Product>> call, Throwable t) { uiHandler.removeMessages(MSG_TIMEOUT); // Show network error message... } }); } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 60
  61. Timeouts with Handler (3) public boolean handleMessage(Message msg) { switch

    (msg.what) { case MSG_TIMEOUT: swipeRefreshLayout.setRefreshing(false); Snackbar.make(findViewById(R.id.content), "Timeout when loading data", Snackbar.LENGTH_LONG) .setAction("Retry", v -> performNetworkSync()) .show(); break; case MSG_PRODUCTS_UPDATED: //noinspection unchecked swipeRefreshLayout.setRefreshing(false); productsAdapter.setProducts((List<Product>) msg.obj); break; } return true; } @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 61
  62. Java Concurrency API • java.util.concurrent.CountDownLatch • java.util.concurrent.LinkedBlockingQueue<E> • android.support.v7.util.AsyncListUtil<T> •

    android.support.v4.view.AsyncLayoutInflater • java.util.concurrent.Executor and friends... @ErikHellman - www.hellsoft.se - erik.hellman@hellsoft.se 62
  63. Support annotations • @MainThread • @WorkerThread • @AnyThread @ErikHellman -

    www.hellsoft.se - erik.hellman@hellsoft.se 63
  64. Key take-aways • Synchronization • Immutability • Cancel/Unregister/Unsubscribe @ErikHellman -

    www.hellsoft.se - erik.hellman@hellsoft.se 64
  65. Thank you for listening! Any questions? @ErikHellman - www.hellsoft.se -

    erik.hellman@hellsoft.se 65