資源混淆是如何影響到Kotlin協程的
導言
隨著kotlin
的使用,協程也慢慢在我們工程中被開始被使用起來,但在我們工程中卻遇到了一個問題,經過資源混淆處理之後的apk包,協程卻不如期工作。那麼兩者到底有什麼關聯呢,資源混淆又是如何影響到協程的使用的,透過閱讀本篇你會馬上知曉。
本篇會從如下幾個方面講述這個問題
問題定義->問題分析->問題解決
問題定義
看下面這段demo程式碼:
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
}
那麼這是為什麼呢?
問題分析
既然跟資源混淆有關,那麼我們看看經過資源混淆之後的apk和之前的apk到底又哪些改變。
資源混淆用的是之前微信開源的的andResguard,簡單來說,資源混淆包括如下幾個步驟:
解壓縮apk
混淆演算法開始混淆res檔案,並改下resources.arsc檔案
用7zip重壓縮apk,重簽名
看起來,1和2對於影響到協程使用可能性很低,那麼3呢,在對比前後apk過程中我們馬上發現混淆前後的apk的METF-INF檔案相差比較大,混淆後只保留了SF,MF,RSA檔案,而混淆前的apk的METF-INF檔案中包含了一些kotlin_module資訊以及services資料夾,那麼會不會和這些檔案的丟失有關呢。
怎麼驗證呢。很簡單,gradle裡面配置packageOptions主動移除META-INF資料夾下的kotlin_module檔案和services資料夾,然後debug除錯一下發現問題復現。那麼肯定和這裡有關啦。
現在先不急著馬上解決它,讓我們看看為啥這幾個檔案的丟失就會導致上面那段協程程式碼工作不正常呢。既然有demo,那我們單步除錯進去看看吧。
上面例子中呼叫了async
函式,透過原始碼可以知道,如果start引數是用的預設的情況下,那麼最後都會走到startCoroutineCancellable
函式,而這個函式內部會呼叫runSafely,內部所有的異常都會被這個函式catch住,所以業務層沒拋crash,直接把這個問題隱藏了,也給快速定位問題加大了難度。
既然用demo復現了這個問題,那麼單步除錯一下,看看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
的,如下圖所示:
看到這個類瞬間明白了,kotlin在編譯的時候,會在META-INF資料夾下生成一個services的資料夾資訊,該資料夾下面放一些支援類的資訊,那麼具體在放了哪些類呢,在原始碼當中有一個pro檔案可以說明一切。
這樣在呼叫相關類的時候會優先先用FastServiceLoader載入該類。一旦載入不到,就會構造一個MissingMainCoroutineDispatcher
,並呼叫missing
方法丟擲異常。
問題解決
經過上述問題分析之後,其實解決方案就非常簡單了。修改資源混淆重打包的流程,在重簽名的時候保留META-INF的servcies資料夾資訊即可
回顧總結
再來回顧一下問題的解決過程,雖然最終解決的方案比較簡單,但有兩個點需要我們特別關注一下
協程當中async內部有try catch機制,所以任何異常都會被內部catch住,而這個在我們開發當中很容易導致一些問題沒有及時發現
在遇到一些奇怪的問題的時候,小而簡單的demo外加原始碼閱讀是必要的,這樣方便我們快速能夠追查到問題原因所在。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557897/viewspace-2661326/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Kotlin的協程Kotlin
- Kotlin Coroutine(協程): 二、初識協程Kotlin
- Kotlin Coroutine(協程): 三、瞭解協程Kotlin
- Android版kotlin協程入門(三):kotlin協程的異常處理AndroidKotlin
- Kotlin 協程一 —— CoroutineKotlin
- Kotlin 之旅7 協程Kotlin
- kotlin協程的掛起suspendKotlin
- DNS 是如何影響你衝浪速度的?DNS
- ChatGPT會如何影響我們的工作生活和人力資源需求ChatGPT
- 雲端計算是如何影響到每個人的生活的?
- 在 Android 開發中使用 Kotlin 協程 (一) -- 初識 Kotlin 協程AndroidKotlin
- Android版kotlin協程入門(四):kotlin協程開發實戰AndroidKotlin
- gorm是如何保證協程安全的GoORM
- CRM系統資料庫是如何影響客戶體驗的?資料庫
- Kotlin Coroutines(協程)講解Kotlin
- Kotlin Coroutine(協程)簡介Kotlin
- Kotlin協程快速進階Kotlin
- Kotlin協程快速入門Kotlin
- Android Kotlin 協程初探AndroidKotlin
- Kotlin 協程使用手冊Kotlin
- Vert.x Kotlin 協程Kotlin
- 物聯網是如何影響我們的生活
- 資料庫調優和資料遷移是如何影響資料庫的RY資料庫
- 【譯】kotlin 協程官方文件(1)-協程基礎(Coroutine Basics)Kotlin
- Android版 kotlin協程入門(二):kotlin協程的關鍵知識點初步講解AndroidKotlin
- 扒一扒Kotlin協程的底褲Kotlin
- 揭開Kotlin協程的神秘面紗Kotlin
- Kotlin協程學習之路【一】Kotlin
- Kotlin Coroutine(協程) 基本知識Kotlin
- Kotlin coroutine之協程基礎Kotlin
- Android Kotlin協程入門AndroidKotlin
- 微信資源混淆工具打包
- 使用 Kotlin + WebFlux/RxJava 2 實現響應式以及嘗試正式版本的協程KotlinWebUXRxJava
- Kotlin 1.4.0-RC協程除錯Kotlin除錯
- 【譯】使用kotlin協程提高app效能KotlinAPP
- [譯] Kotlin 協程高階使用技巧Kotlin
- Kotlin(android)協程中文翻譯KotlinAndroid
- Sirix.io是如何基於Vert.x和Kotlin協程構建非同步RESTful APIKotlin非同步RESTAPI