Home > Software design >  Kotlin: Handler is causing my UI Thread to freeze
Kotlin: Handler is causing my UI Thread to freeze

Time:01-10

I want to call my function indefinitely every 1 second in a specific situation. I'm using AsyncTask to execute my API calls.

I'm using this type of call for some time but this is the first time when it actually blocked my UI Thread and I don't know why.

The handler in the code below is called inside onPostExecute.

protected fun purchaseCheck(transactionId: String){
        app.sysLog("Wait for purchase...")
        task = asyncTask({
            api.checkPaymentStatus(transactionId)
        }, taskName = "Purchase Status") { r ->
            r.js?.let {
                when(r.httpCode){
                    HTTP_PAYMENT_CHECK_PENDING -> {
                        App.log("purchaseCheck: response pending purchase - try again")
                        MainActivity.afterDelay(1000){
                            purchaseCheck(transactionId)
                        }
                    }
                    else -> {
                        App.log("purchaseCheck: response purchase success")
                        onPurchaseSuccessfullyCompleted()
                    }
                }
            }?:kotlin.run {
                when(r.httpCode){
                    HTTP_PAYMENT_CARD_EXPIRED -> {
                        App.log("purchaseCheck: response card expired")
                        showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
                    }
                    else -> {
                        App.log("purchaseCheck: response error (retry)")
                        MainActivity.afterDelay(1000){
                            purchaseCheck(transactionId)
                        }
                    }
                }
            }
        }
    }

Basically

MainActivity.afterDelay(1000){
   purchaseCheck(transactionId)
}

is causing my ProgressBar animation to freeze. When I remove that delay it is working as intended.

Here is afterDelay function:

fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
            class DelayRun : Runnable, Cancellable {
                override fun run() = body()

                override fun cancel() {
                    removePost(this)
                }
            }
            return DelayRun().also {
                post(delay, it)
            }
        }
fun removePost(runnable: Runnable) {
    App.handler.removeCallbacks(runnable)
}

fun post(delay: Int, runnable: Runnable){
    App.handler.postDelayed(runnable, delay.toLong())
}

Handler in Application class:

class App : Application(), Application.ActivityLifecycleCallbacks{

    companion object {
        
        val handler = Handler()
    }
 ...
}

CodePudding user response:

By default, Handler posts tasks on Main (UI) thread. Therefore any job/task you send to your handler will be executed on UI thread - that is the reason why UI freezes - it waits for job to finish before redrawing.

You want to make your handler using another thread. The most simple way is to create HandlerThread.

val handlerThread = new HandlerThread("MyHandlerThread")
handlerThread.start()
val looper = handlerThread.getLooper()
val handler = new Handler(looper)

After these four lines of code, handler will execute it jobs on another thread. But let's take a problem further - you are using AsyncTask, which is deprecated. You also do not want your delay to be counted by afterDelay function, handler can do it for you.

In your case you can just do something like this:

handler.postDelayed(1000, { ... your job ... }).

Getting it together:

protected fun purchaseCheck(transactionId: String){
    app.sysLog("Wait for purchase...")
    val runnable = {
        val status = api.checkPaymentStatus(transactionId)  
        status.js?.let { ... }  
    }
    handler.postDelayed(1000, runnable)
}

I also recommend you to declare handler on Activity level rather than Application. Since you usually don't want it to be global.

  • Related