Kotlin Coroutine Failure After Resource Obfuscation in Android APK
The article explains how resource obfuscation with andResGuard removes META‑INF service files needed for the Android Main dispatcher, causing coroutines to silently fail after withContext calls, and shows that preserving the META‑INF/services directory restores proper coroutine execution.
With the growing adoption of kotlin and coroutines in Android projects, a problem was encountered where coroutines stopped working after the APK underwent resource obfuscation. This article defines the issue, analyses its cause, and provides a solution.
Problem Definition
The following demo code reproduces the issue:
package com.example.coroutinenotworkdemo
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.system.measureTimeMillis
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Job()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
clickid.setOnClickListener {
GlobalScope.async {
Log.i("pisa","start call async")
val cost=measureTimeMillis {
val result=demoSupendFun()
Log.i("pisa","get result=$result")
// After resource obfuscation, the block inside withContext is not executed.
withContext(Dispatchers.Main){
textview.text=result
}
}
Log.i("pisa","cost=$cost")
0
}
Toast.makeText(this,"click result",LENGTH_SHORT)
}
}
suspend fun demoSupendFun(): String {
return suspendCoroutine {
// Simulate an async request and resume with a result
async {
delay(1000)
it.resume("get result")
}
}
}
}After obfuscation, the line textview.text=result never executes.
withContext(Dispatchers.Main){
textview.text=result
}Problem Analysis
The project uses andResGuard for resource obfuscation, which unpacks the APK, obfuscates the res files, modifies resources.arsc , and then repacks and resigns the APK. The repackaging step removes most files in the META-INF directory, including kotlin_module metadata and the services folder.
By configuring Gradle’s packageOptions to deliberately remove those META‑INF entries, the issue can be reproduced, confirming that the missing files are the root cause.
During coroutine execution, the async call eventually reaches startCoroutineCancellable , which invokes runSafely . This method catches all exceptions, so the failure is hidden from the business layer.
Debugging shows that withContext throws the following exception, which is swallowed by runSafely :
Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'The missing MainDispatcher is provided by AndroidDispatcherFactory , which is discovered via a ServiceLoader mechanism. Kotlin generates a services file under META-INF that lists implementations such as AndroidDispatcherFactory . When the services folder is stripped during obfuscation, the loader cannot find the factory, resulting in a MissingMainCoroutineDispatcher and the above exception.
Problem Solution
Keep the META-INF/services directory (or at least the files that declare the AndroidDispatcherFactory ) when repackaging the APK. After preserving these files, the Main dispatcher is found, and the coroutine code inside withContext executes correctly.
Review Summary
Coroutines swallow internal exceptions, which can hide critical issues; developers should be aware of this behavior.
When encountering obscure bugs, a minimal reproducible demo combined with source‑code inspection is essential for rapid diagnosis.
Tencent Music Tech Team
Public account of Tencent Music's development team, focusing on technology sharing and communication.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.