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

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

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.

Erik Hellman

November 03, 2016
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. 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 - [email protected] 11
  2. 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 - [email protected] 12
  3. 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 - [email protected] 15
  4. 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 - [email protected] 16
  5. 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 - [email protected] 17
  6. Multi-threading problem public class Product { public long id; public

    String title; public long price; } @ErikHellman - www.hellsoft.se - [email protected] 18
  7. 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 - [email protected] 19
  8. Fixing it! • Don't share • Make it immutable •

    Use synchronization @ErikHellman - www.hellsoft.se - [email protected] 20
  9. 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 - [email protected] 21
  10. 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 - [email protected] 22
  11. 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 - [email protected] 23
  12. 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 - [email protected] 24
  13. 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 - [email protected] 25
  14. False immutability (1) public class FalseImmutability { private final String[]

    data = new String[] {"one", "two", "three"}; public String[] get() { return data; } } @ErikHellman - www.hellsoft.se - [email protected] 26
  15. 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 - [email protected] 27
  16. Immutability with AutoValue (1) public class Money { public Currency

    currency; public long amount; } @ErikHellman - www.hellsoft.se - [email protected] 28
  17. 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 - [email protected] 29
  18. 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 - [email protected] 30
  19. 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 - [email protected] 31
  20. 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 - [email protected] 32
  21. 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 - [email protected] 34
  22. 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 - [email protected] 35
  23. 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 - [email protected] 36
  24. 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 - [email protected] 37
  25. 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 - [email protected] 38
  26. 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 - [email protected] 41
  27. 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 - [email protected] 43
  28. 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 - [email protected] 44
  29. 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 - [email protected] 46
  30. 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 - [email protected] 47
  31. 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 - [email protected] 48
  32. 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 - [email protected] 49
  33. 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 - [email protected] 51
  34. How to use AsyncTask (2) protected void onDestroy() { super.onDestroy();

    // Very, very, very important! myTask.cancel(false); } @ErikHellman - www.hellsoft.se - [email protected] 52
  35. 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 - [email protected] 53
  36. 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 - [email protected] 54
  37. 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 - [email protected] 55
  38. 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 - [email protected] 56
  39. 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 - [email protected] 57
  40. 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 - [email protected] 58
  41. 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 - [email protected] 59
  42. 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 - [email protected] 60
  43. 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 - [email protected] 61
  44. 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 - [email protected] 62