隨着kotlin
的使用,協程也慢慢在咱們工程中被開始被使用起來,但在咱們工程中卻遇到了一個問題,通過資源混淆處理以後的apk包,協程卻不如期工做。那麼二者到底有什麼關聯呢,資源混淆又是如何影響到協程的使用的,經過閱讀本篇你會立刻知曉。java
本篇會從以下幾個方面講述這個問題android
問題定義->問題分析->問題解決git
看下面這段demo代碼:github
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裏面的塊沒獲得執行。。
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 {
//模擬一個異步請求,而後回調,獲得結果
async {
delay(1000)
it.resume("get result")
}
}
}
}
複製代碼
咱們發現通過資源混淆以後,下面這段代碼中,textview.text=result
始終沒有獲得執行。算法
withContext(Dispatchers.Main){
textview.text=result
}
複製代碼
那麼這是爲何呢?bash
既然跟資源混淆有關,那麼咱們看看通過資源混淆以後的apk和以前的apk到底又哪些改變。 資源混淆用的是以前微信開源的的[andResguard][1],簡單來講,資源混淆包括以下幾個步驟:微信
看起來,1和2對於影響到協程使用可能性很低,那麼3呢,在對比先後apk過程當中咱們立刻發現混淆先後的apk的METF-INF文件相差比較大,混淆後只保留了SF,MF,RSA文件,而混淆前的apk的METF-INF文件中包含了一些kotlin_module信息以及services文件夾,那麼會不會和這些文件的丟失有關呢。 app
怎麼驗證呢。很簡單,gradle裏面配置packageOptions主動移除META-INF文件夾下的kotlin_module文件和services文件夾,而後debug調試一下發現問題復現。那麼確定和這裏有關啦。異步
如今先不急着立刻解決它,讓咱們看看爲啥這幾個文件的丟失就會致使上面那段協程代碼工做不正常呢。既然有demo,那咱們單步調試進去看看吧。async
上面例子中調用了async
函數,經過源碼能夠知道,若是start參數是用的默認的狀況下,那麼最後都會走到startCoroutineCancellable
函數,而這個函數內部會調用runSafely,內部全部的異常都會被這個函數catch住,因此業務層沒拋crash,直接把這個問題隱藏了,也給快速定位問題加大了難度。
withContext
裏面到底掛在了哪裏?最終調試發現,果真這裏
runSafely
裏面catch住了一個exception,異常信息以下:
Module with the Main dispatcher is missing.Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android
因此上面withContext裏面的代碼就沒有執行到了。
那麼這裏的MainDispatcher是什麼呢?原來是在調用withContext來切換線程的時候,會用到類MainCoroutineDispatcher
。這個類是個抽象類,會通過MainDispatcherFactory
工廠來建立具體的dispatcher,在Android上是AndroidDispatcherFactory
來負責建立,MainDispatcherFactory
這個類是經過自定義的ServiceLoader加載進來的,在kotlin中定義了一個FastServiceLoader
,這個類與java的ServiceLoader最大的區別是跳過了jar的校驗,能夠直接從jar包中加載某一個類的信息,若是用常規的ServiceLoader是須要讀取整個jar包以後,在定位到對應的class文件信息,加載進來,這整個過程是一個很是耗時的操做,可能致使android設備發生ANR的現象。
看看FastServiceLoader是如何加載AndroidDispatcherFactory
的,以下圖所示:
MissingMainCoroutineDispatcher
,並調用
missing
方法拋出異常。
通過上述問題分析以後,其實解決方案就很是簡單了。修改資源混淆重打包的流程,在重簽名的時候保留META-INF的servcies文件夾信息便可
再來回顧一下問題的解決過程,雖然最終解決的方案比較簡單,但有兩個點須要咱們特別關注一下