内容简介:随着本篇会从如下几个方面讲述这个问题问题定义->问题分析->问题解决
导言
随着 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外加源码阅读是必要的,这样方便我们快速能够追查到问题原因所在。
PS 重磅消息!我们 招人 啦!!
腾讯音乐 技术团队招程序猿啦
不管你是 研发 大神还是 测试 大佬 只要你有意向加入我们团队,那就赶紧发送简历到
tmezp@tencent.com ,
记得注明来自公众号哦~我们会优先拜读
腾讯音乐技术团队期待与你一起music!:musical_note::musical_note:
以上所述就是小编给大家介绍的《资源混淆是如何影响到Kotlin协程的》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据挖掘导论
Pang-Ning Tan、Michael Steinbach、Vipin Kumar / 范明、范宏建 / 人民邮电出版社 / 2010-12-10 / 69.00元
本书全面介绍了数据挖掘,涵盖了五个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都有两章。前一章涵盖基本概念、代表性算法和评估技术,而后一章讨论高级概念和算法。这样读者在透彻地理解数据挖掘的基础的同时,还能够了解更多重要的高级主题。 本书是明尼苏达大学和密歇根州立大学数据挖掘课程的教材,由于独具特色,正式出版之前就已经被斯坦福大学、得克萨斯大学奥斯汀分校等众多名校采用。 ......一起来看看 《数据挖掘导论》 这本书的介绍吧!