Mobile Development 8 min read

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.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Kotlin Coroutine Failure After Resource Obfuscation in Android APK

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.

DebuggingAndroidAPKKotlincoroutinesResource Obfuscation
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.