Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Android Async Programming

Hadi Tok
April 25, 2021
63

Android Async Programming

Presentation about Threading in Android. Topics : MessageQueue, Looper, Hander, Futures, Thread Safety, Cancellation, Coroutines, Flow.

Hadi Tok

April 25, 2021
Tweet

Transcript

  1. Threads A thread of execution is the smallest sequence of

    programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.
  2. Threads A thread of execution is the smallest sequence of

    programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.
  3. Threads A thread of execution is the smallest sequence of

    programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.
  4. Threads A thread of execution is the smallest sequence of

    programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.
  5. public static void main(String[] args) { Thread thread = new

    Thread() { @Override public void run() { System.out.println("this is a thread"); } }; thread.start(); }
  6. Threads They are relatively expensive in java. Shouldn’t need to

    create threads for every sequence of instructions we run.
  7. Threads They are relatively expensive in java. Shouldn’t need to

    create threads for every sequence of instructions we run. To prevent this we have …
  8. Runnables • A thread can run multiple runnables • The

    runnables can be provided from the same thread or could be provided from external threads.
  9. Runnables • A thread can run multiple runnables • The

    runnables can be provided from the same thread or could be provided from external threads. • This is not provided out of the box.
  10. What is main thread? it is in charge of dispatching

    events to the appropriate user interface widgets, including drawing events. It is also almost always the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages)
  11. Runnables • A thread can run multiple runnables • The

    runnables can be provided from the same thread or could be provided from external threads. • This is not provided out of the box.
  12. Runnables • Main thread can run multiple runnables • The

    runnables can be provided from the main thread or could be provided from background threads. • This is not provided out of the box.
  13. for ( ;; ) { Message msg = queue.next(); //

    might block … msg.target.dispatchMessage(msg); … msg.recycleUnchecked(); }
  14. public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null)

    { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
  15. MessageQueue Low-level class holding the list of messages to be

    dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.
  16. MessageQueue Low-level class holding the list of messages to be

    dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper. 🤔
  17. What is Handler A Handler allows you to send and

    process Message and Runnable objects associated with a thread's MessageQueue
  18. Main uses for handler • to enqueue an action to

    be performed on a different thread than your own
  19. Main uses for handler • to enqueue an action to

    be performed on a different thread than your own • to schedule messages and runnables to be executed at some point in the future
  20. postAtFrontOfQueue() Posts a message to an object that implements Runnable.

    This method is only for use in very special circumstances -- it can easily starve the message queue, cause ordering problems, or have other unexpected side-effects.
  21. postDelayed() Causes the Runnable r to be added to the

    message queue, to be run after the speci fi ed amount of time elapses. The time-base is SystemClock.uptimeMillis()
  22. postAtTime() Causes the Runnable r to be added to the

    message queue, to be run at a speci fi c time given by uptimeMillis. The time-base is SystemClock.uptimeMillis()
  23. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } }
  24. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } }
  25. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } 🤔
  26. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) textView.postDelayed(100){ Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } }
  27. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) textView.postDelayed(100){ Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } }
  28. ExecutorService An Executor that provides methods to manage termination and

    methods that can produce a Future for tracking progress of one or more asynchronous tasks.
  29. ExecutorService An Executor that provides methods to manage termination and

    methods that can produce a Future for tracking progress of one or more asynchronous tasks.
  30. ExecutorService An Executor that provides methods to manage termination and

    methods that can produce a Future for tracking progress of one or more asynchronous tasks.
  31. Future A Future represents the result of an asynchronous computation.

    Methods are provided to check if the computation is complete
  32. Future A Future represents the result of an asynchronous computation.

    Methods are provided to check if the computation is complete, to wait for its completion,
  33. Future A Future represents the result of an asynchronous computation.

    Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.
  34. Future The result can only be retrieved using method get

    when the computation has completed,
  35. Future The result can only be retrieved using method get

    when the computation has completed, blocking if necessary until it is ready.
  36. fun waitAndToastRandomInt() { val futureTask = FutureTask { val randomInt

    = Random.nextInt(10) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask) Toast.makeText(this, "lucky number is ${futureTask.get()}” , Toast.LENGTH_LONG ).show() }
  37. fun waitAndToastRandomInt() { val futureTask = FutureTask { val randomInt

    = Random.nextInt(10) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask) Toast.makeText(this, "lucky number is ${futureTask.get()}” , Toast.LENGTH_LONG ).show() }
  38. fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create<Int>() executor.submit { val

    randomInt = Random.nextInt(10) Thread.sleep(randomInt * 1000L) listenableFuture.set(randomInt) } listenableFuture.addListener({ Toast.makeText(this, "lucky number is ${listenableFuture.get()}", Toast.LENGTH_LONG ).show() }, ContextCompat.getMainExecutor(this))
  39. fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create<Int>() executor.submit { val

    futureTask1 = (FutureTask { val randomInt = Random.nextInt(5) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt }) val futureTask2 = FutureTask { val randomInt = Random.nextInt(5) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask1) executor.submit(futureTask2) listenableFuture.set(futureTask1.get() + futureTask2.get()) } listenableFuture.addListener({ Toast.makeText(this, "lucky number is ${listenableFuture.get()}", Toast.LENGTH_LONG ).show() }, ContextCompat.getMainExecutor(this)) }
  40. fun threadSafety() { val list = mutableListOf(1) executor.submit { Thread.sleep(Random.nextLong(100))

    if (list.isEmpty().not()) { list.remove(0); } } executor.submit { Thread.sleep(Random.nextLong(100)) if (list.isEmpty().not()) { list.remove(0); } } }
  41. fun immutability() { var list = listOf(1, 2, 3) executor.submit

    { Thread.sleep(Random.nextLong(100)) val newList = list.toMutableList() if (newList.isEmpty().not()) { newList.remove(0) list = newList.toList() } } executor.submit { Thread.sleep(Random.nextLong(100)) val newList = list.toMutableList() if (newList.isEmpty().not()) { newList.remove(0) list = newList.toList() } } }
  42. fun syncronizedCollections() { val list = mutableListOf<Int>() executor.submit { Thread.sleep(Random.nextLong(100))

    list.addAll(listOf(10,20,30)) } executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(50,60,70)) } }
  43. fun syncronizedCollections() { val list = Collections.synchronizedList(mutableListOf<Int>()) executor.submit { Thread.sleep(Random.nextLong(100))

    list.addAll(listOf(10,20,30)) } executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(50,60,70)) } }
  44. private val list = mutableListOf<Int>() fun syncronizedFunctions() { executor.submit {

    Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items:List<Int>){ items.forEach { list.add(it) } }
  45. private val list = mutableListOf<Int>() fun syncronizedFunctions() { executor.submit {

    Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } @Synchronized fun addAllToList(items:List<Int>){ items.forEach { list.add(it) } }
  46. private val list = mutableListOf<Int>() fun syncronizedBlocks() { executor.submit {

    Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items:List<Int>){ items.forEach { list.add(it) } }
  47. private val list = mutableListOf<Int>() fun syncronizedBlocks() { executor.submit {

    Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items: List<Int>) { synchronized(list) { items.forEach { list.add(it) } } Thread.sleep(Random.nextLong(100)) print(“asd”) }
  48. Long running tasks • Challenge 1: Execute on a non-UI

    thread • Challenge 2: Get informed when the task is done
  49. fun login(username: String, pass: String) { Thread { requestLogin(username, pass)

    { result -> Handler(Looper.getMainLooper()).post { showResult(result) } } }.run() }
  50. fun showResult(result: Result) { when (result) { is Result.Success ->

    { Toast.makeText( this, "Login Success", Toast.LENGTH_SHORT ).show() } is Result.Failure -> { Log.d("TAG", result.error.message, result.error) Toast.makeText( this, "Login failure ${result.error.message}", Toast.LENGTH_SHORT ).show() } } }
  51. onDraw onDraw onDraw requestLogin UI Thread fun login(name, pass) {

    requestLogin(name, pass) { result -> show(result) } }
  52. onDraw onDraw onDraw requestLogin UI Thread fun login(name, pass) {

    requestLogin(name, pass) { result -> show(result) } }
  53. onDraw onDraw onDraw requestLogin UI Thread show onDraw fun login(name,

    pass) { requestLogin(name, pass) { result -> show(result) } }
  54. onDraw onDraw onDraw requestLogin UI Thread show onDraw fun login(name,

    pass) { requestLogin(name, pass) { result -> show(result) } } onDraw onDraw onDraw
  55. fun login(username: String, pass: String) = GlobalScope.launch { val result

    = requestLogin(username, pass) showResult(result) }
  56. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.IO) { val result

    = requestLogin(username, pass) withContext(Dispatchers.Main) { showResult(result) } }
  57. suspend fun requestLogin(username: String, pass: String) : Result = withContext(Dispatchers.IO)

    { delay(1000) // long running operation return@withContext Result.Success }
  58. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  59. Dispatchers.Default • Common pool of shared background threads • Use

    it for: computing-intensive coroutines Dispatchers.Main • Main thread • Use it for: UI operations
  60. Dispatchers.Default • Common pool of shared background threads • Use

    it for: computing-intensive coroutines Dispatchers.IO • Shared pool of on-demand created threads • Use it for: IO-intensive blocking operations Dispatchers.Main • Main thread • Use it for: UI operations
  61. Dispatchers.Default • Common pool of shared background threads • Use

    it for: computing-intensive coroutines Dispatchers.Unconfined • Doesn’t confine the coroutine to any specific thread • Don’t use it in code Dispatchers.IO • Shared pool of on-demand created threads • Use it for: IO-intensive blocking operations Dispatchers.Main • Main thread • Use it for: UI operations
  62. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  63. try { val token = auth(username, pass) val result =

    requestLogin(token) showResult(result) } catch (e: Exception) { }
  64. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  65. fun login(username: String, pass: String) = uiScope.launch { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  66. val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job)

    fun login(username: String, pass: String) = uiScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() }
  67. val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job)

    fun login(username: String, pass: String) = uiScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() } lifecycle-viewmodel-ktx
  68. val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job)

    fun login(username: String, pass: String) = viewModelScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() }
  69. fun login(username: String, pass: String) = viewModelScope.launch { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  70. val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var

    nextPrintTime = startTime var i = 0 while (i < 5) { // computation loop, just wastes CPU // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i ++ } ... ") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")
  71. job: I'm sleeping 0 ... job: I'm sleeping 1 ...

    job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm sleeping 3 ... job: I'm sleeping 4 ... main: Now I can quit.
  72. val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var

    nextPrintTime = startTime var i = 0 while (isActive) { // cancellable computation loop // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i ++ } ... ") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")
  73. job: I'm sleeping 0 ... job: I'm sleeping 1 ...

    job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit.
  74. val job = launch { try { repeat(1000) { i

    -> println("job: I'm sleeping $i ... ") delay(500L) } } finally { println("job: I'm running finally") } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")
  75. suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are

    doing something useful here return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // pretend we are doing something useful here, too return 29 }
  76. val time = measureTimeMillis { val one = doSomethingUsefulOne() val

    two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms”)
  77. val time = measureTimeMillis { val one = doSomethingUsefulOne() val

    two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms”) // The answer is 42 // Completed in 2007 ms
  78. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")
  79. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms”) // The answer is 42 // Completed in 1017 ms
  80. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")
  81. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() }.await() val two = async { doSomethingUsefulTwo() }.await() println("The answer is ${one + two}") } println("Completed in $time ms")
  82. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() }.await() val two = async { doSomethingUsefulTwo() }.await() println("The answer is ${one + two}") } println("Completed in $time ms")
  83. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")
  84. Observer pattern A software design pattern in which an object,

    named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
  85. Observer pattern It is mainly used for implementing distributed event

    handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",
  86. Observer pattern It is mainly used for implementing distributed event

    handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",
  87. Observer pattern It is mainly used for implementing distributed event

    handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",
  88. Observer pattern It is mainly used for implementing distributed event

    handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",
  89. Flow Using the List<Int> result type, means we can only

    return all the values at once. To represent the stream of values that are being asynchronously computed, we can use a Flow<Int> type just like we would use the Sequence<Int> type for synchronously computed values
  90. fun simple(): Flow<Int> = flow { // flow builder for

    (i in 1 .. 3) { delay(100) // pretend we are doing something useful here emit(i) // emit next value } }
  91. fun main() = runBlocking<Unit> { / / Launch a concurrent

    coroutine to check if the main thread is blocked launch { for (k in 1 .. 3) { println("I'm not blocked $k") delay(100) } } / / Collect the flow simple().collect { value -> println(value) } } fun simple(): Flow<Int> = flow { / / flow builder for (i in 1 . . 3) { delay(100) // pretend we are doing something useful here emit(i) / / emit next value } }
  92. Flows are cold Flows are cold streams similar to sequences

    — the code inside a flow builder does not run until the flow is collected. This becomes clear in the following example:
  93. fun simple(): Flow<Int> = flow { println("Flow started") for (i

    in 1 .. 3) { delay(100) emit(i) } } fun main() = runBlocking<Unit> { println("Calling simple function ... ") val flow = simple() println("Calling collect ... ") flow.collect { value -> println(value) } println("Calling collect again ... ") flow.collect { value -> println(value) } }
  94. Calling simple function ... Calling collect ... Flow started 1

    2 3 Calling collect again ... Flow started 1 2 3
  95. fun simple(): Flow<Int> = flow { println("Flow started") for (i

    in 1 .. 3) { delay(100) emit(i) } } fun main() = runBlocking<Unit> { println("Calling simple function ... ") val flow = simple() println("Calling collect…”) flow.collect { value -> println(value) } println("Calling collect again ... ") flow.collect { value -> println(value) } }
  96. Calling simple function ... Calling collect ... Flow started 1

    2 3 Calling collect again ... Flow started 1 2 3
  97. Flows are cold This is a key reason the simple

    function (which returns a flow) is not marked with suspend modifier. By itself, simple() call returns quickly and does not wait for anything. The flow starts every time it is collected, that is why we see "Flow started" when we call collect again.
  98. suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running

    asynchronous work return "response $request" } fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a flow of requests .map { request -> performRequest(request) } .collect { response -> println(response) } }
  99. suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running

    asynchronous work return "response $request" } fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a flow of requests .map { request -> performRequest(request) } .collect { response -> println(response) } } response 1 response 2 response 3
  100. suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running

    asynchronous work return "response $request" } fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a flow of requests .transform { request -> emit("Making request $request") emit(performRequest(request)) } .collect { response -> println(response) } }
  101. suspend fun performRequest(request: Int): String { delay(1000) / / imitate

    long-running asynchronous work return "response $request" } fun main() = runBlocking<Unit> { (1 . . 3).asFlow() // a flow of requests .transform { request -> emit("Making request $request") emit(performRequest(request)) } .collect { response - > println(response) } } Making request 1 response 1 Making request 2 response 2 Making request 3 response 3
  102. fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a

    flow of requests .take(2) .collect { value -> println(value) } }
  103. fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a

    flow of requests .take(2) .collect { value -> println(value) } } 1 2
  104. fun main() = runBlocking<Unit> { // Collect the flow simple().collect

    { value -> println(value) } } fun simple(): Flow<Int> = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } }
  105. fun main() = runBlocking<Unit> { // Collect the flow simple().collect

    { value -> println(value) } } fun simple(): Flow<Int> = flow { kotlinx.coroutines.withContext(Dispatchers.Default) { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } } }
  106. Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated: Flow

    was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323], but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default]. Please refer to 'flow' documentation or use 'flowOn' instead at ...
  107. fun main() = runBlocking<Unit> { // Collect the flow simple().collect

    { value -> println(value) } } fun simple(): Flow<Int> = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } }.flowOn(Dispatchers.Default)