资源混淆是如何影响到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外加源码阅读是必要的,这样方便我们快速能够追查到问题原因所在。
Image placeholder
jiacongcong
未设置
  19人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Kotlin如何安全访问lateinit变量

Kotlin设计之初就是不允许非null变量在声明期间不进行初始化的,为了解决这个问题,Kotlinlateinit允许我们先声明一个变量,然后在程序执行周期的将来某个时候将其初始化,让编译检查时不会

学习 nodejs+mongodb+koa2 写接口(二) koa2教程入门

一.hellokoa安装koa2#初始化package.json npminit #安装koa2 npminstallkoahelloworld代码constKoa=require('koa') c

Java 与 Kotlin 系列文章 (一):性能问题

随着对Kotlin越来越深入的了解,我发现市面上关于Kotlin方面,比较深入的资料几乎是0,所以我决定,将Kotlin各个方面的研究作为我的研究生课题,而性能问题往往是程序员最佳关注的内容,所以第

koala如何压缩css?

koala如何压缩css?koala是一个前端预处理器语言图形编译工具,支持Less、Sass、Compass、CoffeeScript,帮助web开发者更高效地使用它们进行开发。跨平台运行,完美兼容

采用 PHP-quickorm/Captcha,用最快的速度在 PHP 语言下实现验证码功能

要调用起这个库,门槛十分低,但是建议满足以下几个条件: PHP5+ PHPGD扩展 Composer(非必须) 安装方法 首先我们花30秒来引入一下这个库,主要有以下两种方式。 其一、使用Comp

集成 think-ORM 的 symfony bundle thinkorm-bundle

thinkorm-bundleSymfonyThinkOrmBundle关于thinkorm-bundle允许在你symfony使用thinkorm.所安装$composerrequireccwwwo

使用 ES6 写 Koa Web 项目

完整代码:传送门我们node.js只是实现了部分ES6的语法,所以为了让我们ES6的代码能100%在node.js下执行,必须使用我们的babel把ES6代码编译成nodejs可执行的代码。node环

实现koa核心代码

导语:实现简单的koa核心代码,便于理解koa原理。顺便学习koa代码的那些骚操作。简单分析koa创建一个http服务,只绑一个中间件。创建index.js/**index.js*/ constKoa

Koa源码阅读

查看Koa,version@2.7源码,总共只有四个文件application.js、context.js、request.js、response.js;分别对应Koa应用入口、上下文环境、请求对象和

学习 nodejs+mongodb+koa2 写接口(一) 环境布置

一.环境准备最近在学用Nodejs写后端接口,了解到koa2是Nodejs的一个框架。可以快速开发后端接口,同时也能更快熟悉Nodejs以下是所需的环境node  v7.6+,可以用nvm或者n安装指

如何避免人类偏见对数据分析产生影响

随着越来越多的企业开始采用机器学习技术以实现流程的自动化,人们也逐渐开始质疑计算机决策中的伦理含义。我们如何处理计算机系统中潜在的偏见?相对较少被提及但同样重要的,是人类本身的偏见,它与分析和商业决策

python 协程

Python中的协程和生成器很相似但又稍有不同。主要区别在于: 生成器是数据的生产者 协程则是数据的消费者 首先我们先来回顾下生成器的创建过程。我们可以这样去创建一个生成器: deffib():

🚀 Hyperf 发布 v1.1.8 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容 新增 #965新增RedisLua模块,用于管理Lua脚本; #1023hyperf/metric组件的Prometheus驱动新增CUSTOM_MODE模式; 修复 #1013修复Js

🚀 Hyperf 发布 v1.1.9 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容 本周更新主要为DI组件新增了懒加载功能,配置为懒加载后,注入的对象为一个代理对象,在使用到时,才会实现对象的初始化。以及为DIContainer增加了set和define方法来动态的增加对象

🚀 Hyperf 发布 v1.1.9 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容本周更新主要为DI组件新增了懒加载功能,配置为懒加载后,注入的对象为一个代理对象,在使用到时,才会实现对象的初始化。以及为DIContainer增加了set和define方法来动态的增加对象管

Golang语言的主要特性与发展的环境和影响因素

1.2.1影响Go语言发展的早期编程语言 正如“21世纪的C语言”这句话所说,Go语言并不是凭空而造的,而是和C++、Java和C#一样属于C系。不仅如此,设计者们还汲取了其它编程语言的精粹部分融入

最新安卓零日漏洞被曝出,或影响谷歌、华为和小米等品牌手机

近日,据外媒BleepingComputer报道,谷歌威胁分析团队(TAG)称,一个最新的安卓0day漏洞或被用于攻击谷歌Pixel、华为、小米和三星以及OPPO等智能手机。 该漏洞是由谷歌Pro

1000 行 Python 代码脚本 bug,或影响上百篇学术论文

《Nature》杂志2014年的一篇论文包含了一个Python脚本,其中有一个模块是根据文件的排序返回值,但Python并没有定义查询的文件顺序。这意味着在不同的操作系统上,该脚本返回的值是不同的。

大数据对移动应用开发的影响

大数据如何影响移动应用程序开发?目前,数据量正以前所未有的速度在增长。由于产生的总数据将在几年内跨越泽字节级别,因此更加需要进行大数据高级分析,并从庞大的数据池中获取有价值的信息。数字互联为移动应用开

近50年来最具影响力的10种编程语言,都是谁发明的?

大数据文摘出品编译:洪颖菲、武帅前不久文摘菌曾报道过4分钟看尽Top15编程语言15年来的沉浮史,评论中就有小伙伴留言了为什么Ruby、Lisp这些语言在榜上寂寂无名?软件世界中有各种各样的编程语言,

关于分析IT系统宕机对业务影响的10个提示

在制定灾难恢复计划时,一个非常重要的任务就是,要确定并想方设法避免潜在的威胁,同时为最坏的情况做准备。业务影响分析(BIA)提供了解决突发事件所需的信息,前提是您要事先做好万全准备。遵循详尽的业务影响

10后小学生都能教你学编程了!低龄编程的下限在哪?

大数据文摘出品作者:宁静最近,文摘菌经常收到读者留言,说b站上有一个10后小学生在教编程。小学生???教编程???话说文摘菌小学时候还只知道玩儿贪吃蛇……在感叹长江后浪推前浪的同时,文摘菌也赶紧去这位

Peloton:优步开源的统一资源调度器

Peloton最初是在2018年11月份引入的,并在2019年3月份正式开源。Peloton是为像优步这样拥有数百万个容器和数万个节点的规模公司设计的,它提供了高级的资源管理特性,比如弹性资源共享、层

PHP 内核:foreach 是如何工作的?

foreach是如何工作的? 首先声明,我知道foreach是什么,也知道怎么去用它。但这个问题关心的是,内核中foreach是如何运行的,我不想回答关于“如何使用foreach循环数组”的任何问题。

Kafka 优秀的架构设计!它的高性能是如何保证的?

应大部分的小伙伴的要求,今天这篇咱们用大白话带你认识Kafka。Kafka 基础消息系统的作用大部分小伙伴应该都清楚,这里用机油装箱举个例子:所以消息系统就是如上图我们所说的仓库,能在中间过程作为缓存