The introduction

With the use of Kotlin, coroutines gradually began to be used in our project, but we encountered a problem in our project. After resource confusion processing, the apK package coroutines did not work as scheduled. Read on to find out how the two are related, and how resource confusion affects coroutine use.

This article will address this issue from the following aspects

Problem definition -> Problem Analysis -> Problem resolution

Problem definition

Take a look at the following demo code:

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") 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 asynchronous request and then call back to get the result async {delay(1000) it. Resume ("get result")}}}}Copy the code

Textview.text =result is never executed in this code after resource obfuscations.

withContext(Dispatchers.Main){
    textview.text=result
}
Copy the code

So why is this?

Problem analysis

Since it is related to resource confusion, let’s take a look at how apK changes after resource confusion and before APK. Resource confusion uses the previous wechat open source [andResguard][1]. To put it simply, resource confusion includes the following steps:

  1. Unzip the apk
  2. The obfuscation algorithm starts obfuscating the RES file and changes the resources. Arsc file
  3. Re-compress APK with 7ZIP and re-sign

It seems that 1 and 2 are very unlikely to affect the use of coroutines. As for 3, in the process of comparing apK before and after the confusion, we immediately found that the metF-INF files of APK before and after the confusion were quite different, and only SF, MF and RSA files were retained after the confusion. The metf-INF file of apK contains some kotlin_module information and the services folder.

So how do we test that. Gradle: Configure packageOptions to remove the “kotlin_module” and “services” files from the “meta-INF” folder. So it must have something to do with this.

Before we solve it right now, let’s see why the coroutine code above doesn’t work properly if these files are missing. Since there is a demo, let’s step into the debugging look.

In the above example is called the async function, through the source code, can know if the start parameter is used by default, so the last will all come to startCoroutineCancellable function, and internal runSafely will call this function, All internal exceptions will be caught by this function, so the business layer does not throw crash, but directly hides the problem, which also makes it more difficult to quickly locate the problem.

withContext
runSafely
Module with the Main dispatcher is missing.Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android

So what is the MainDispatcher here? MainCoroutineDispatcher is used when you call withContext to switch threads. This class is an abstract class that uses the MainDispatcherFactory to create a specific dispatcher. On Android, the AndroidDispatcherFactory is responsible for creating a specific dispatcher. The MainDispatcherFactory class is loaded with a custom ServiceLoader. Kotlin defines a FastServiceLoader. The main difference between this class and Java ServiceLoader is that it skips jar validation. You can load the information about a class directly from the JAR package. If you use a regular ServiceLoader, you need to read the entire JAR package, locate the corresponding class file, and load it. This process is time-consuming and may cause ANR problems on Android devices.

See how FastServiceLoader loads AndroidDispatcherFactory as shown below:

The source code

MissingMainCoroutineDispatcher
missing

Problem solving

After analyzing the above problems, the solution is actually very simple. Modify the process of resource obturation repackaging to retain the servcies folder information of the META-INF during resigning

review

To review the process of solving the problem, although the final solution is relatively simple, there are two points that need to be paid special attention to

  1. In the coroutine async, there is an internal try catch mechanism, so any exception will be caught by the internal catch, which may easily lead to some problems not found in time in our development
  2. When we encounter some strange problems, a small and simple demo plus a source code reading is necessary so that we can quickly track down the cause of the problem.