中超联赛广州恒大门票9月13日|中超积分榜16轮
開發

資源混淆是如何影響到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,簡單來說,資源混淆包括如下幾個步驟:

  1. 解壓縮apk
  2. 混淆算法開始混淆res文件,并改下resources.arsc文件
  3. 用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文件夾信息即可

回顧總結

再來回顧一下問題的解決過程,雖然最終解決的方案比較簡單,但有兩個點需要我們特別關注一下

  1. 協程當中async內部有try catch機制,所以任何異常都會被內部catch住,而這個在我們開發當中很容易導致一些問題沒有及時發現
  2. 在遇到一些奇怪的問題的時候,小而簡單的demo外加源碼閱讀是必要的,這樣方便我們快速能夠追查到問題原因所在。
我還沒有學會寫個人說明!

干了5年程序員,該如何轉行?5個新工作方向了解一下

上一篇

回饋開源,我如何排查一個MySQL Bug

下一篇

你也可能喜歡

資源混淆是如何影響到Kotlin協程的

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃
中超联赛广州恒大门票9月13日 贵州11选5走势图 哪个app有福州麻将 北京赛车 好运彩3 青海11选五5全单开奖前后 大发排列3怎么玩 闲来湖南麻将外挂 中国竞彩篮球比分直播网 竞彩足球比分推荐预测 雪园nba比分直播 山西十一选五 pk10精准高手计 正宗杭州麻将单机版 足球比分网 逛球街 天津十一选五 广东十一选五分布走