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

導言

隨着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. 解壓縮apk
  2. 混淆算法開始混淆res文件,並改下resources.arsc文件
  3. 用7zip重壓縮apk,重簽名

看起來,1和2對於影響到協程使用可能性很低,那麼3呢,在對比先後apk過程當中咱們立刻發現混淆先後的apk的METF-INF文件相差比較大,混淆後只保留了SF,MF,RSA文件,而混淆前的apk的METF-INF文件中包含了一些kotlin_module信息以及services文件夾,那麼會不會和這些文件的丟失有關呢。 app

混淆前和混淆後META-INF文件夾差別

怎麼驗證呢。很簡單,gradle裏面配置packageOptions主動移除META-INF文件夾下的kotlin_module文件和services文件夾,而後debug調試一下發現問題復現。那麼確定和這裏有關啦。異步

如今先不急着立刻解決它,讓咱們看看爲啥這幾個文件的丟失就會致使上面那段協程代碼工做不正常呢。既然有demo,那咱們單步調試進去看看吧。async

上面例子中調用了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外加源碼閱讀是必要的,這樣方便咱們快速可以追查到問題緣由所在。
相關文章
相關標籤/搜索