android代码混淆个人总结及踩坑

android代码混淆个人总结及踩坑

前言

公司项目使用组件化开发的形式,需要对自己负责的模块进行一些混淆配置,关于混淆相信做android开发的都或多或少有过一些接触,通过对混淆文件的配置从而将代码中的类名,方法名,成员变量等进行无意义的字符替换达到增加反编译难度的作用,这篇文章主要要说的问题就是在学习混淆过程中自己的一些总结想法。

混淆语法

这个网上有很多关于这块的资料,说的也比较清楚了,无非就是keep ,keep class,keep classmembers,*,...等混淆命令的使用,没必要去强记,使用到的时候去查找下就好了,规则还是比较简单,在实际项目开发中用到的混淆命令实际上都大同小异。这里重点说下自己关注过的几个混淆命令

-renamesourcefileattribute SourceFile

这个混淆命令的作用就是将源代码中有意义的类名转换成SourceFile,从而达到混淆具体崩溃代码的目的。当你在混淆规则中加上这句混淆命令后崩溃日志将会变成这样

 

QQ截图20180926100635.png

 

可以看到具体的类名全部被SourceFile给替换掉了,如果将-renamesourcefileattribute SourceFile给注释掉重新运行下程序那么具体崩溃的类名就能显示出来,在具体配置混淆规则时可以视情况考虑是否使用该混淆命令。

-keep class和-keep keepclasseswithmembers

被坑过的混淆命令,当时自己要做的混淆是将一个类以及类中所有内容都给keep住不被混淆,使用的混淆命令是

-keep classeswithmembers *{*;}

编译运行后发现并没有效果,后来发现必须使用

-keep class *{*;}

才有效果,算是小坑一把,所以还是介意在写一些混淆命令的时候最好参考下模板混淆命令,不然很可能会造成不该混淆的被混淆这种意外。

-assumenosideeffects

这个混淆命令看着有点使用价值,网上常见的使用场景就是通过该命令在打release包的时候去掉代码中的log日志,模板如下

-assumenosideeffects class android.util.Log{
    public static *** v(...);
    public static *** i(...);
    public static *** d(...);
    public static *** w(...);
    public static *** e(...);
}

上面混淆命令的意思就是会将Log中这五个api调用从代码中直接删除从而达到release包不会泄露日志信息的作用,看起来还是一个非常实用的命令,需要注意一点的就是这个命令必须将-dontoptimize给关闭掉,关闭的配合也很简单在gradle中配置即可

            //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

将默认的proguard-android.txt给替换成proguard-android-optimize.txt,这两个混淆文件是gradle给我们提供好的默认文件,会在里面进行一些通用混淆的配置,两个文件最主要的区别就在于一个是关闭了优化,另一个则是开启优化。经过上面assumenosideeffects和proguard-android-optimize.txt配置,在新建的demo工程运行后发现这配置确实管用,代码中的log日志全部给去掉了,但是在将这些配置应用到实际项目后发现居然失效了!!!日志照样还是会打印出来,自己也进行过问题查找,折腾了半天只能说无功而返,怀疑和多模块之间复杂的依赖有关导致配置失效。

#######小总结
(1)使用assumenosideeffects最好确认下日志是否真的被清除了,可能在实际应用中会遇到和我一样的问题。
(2)使用assumenosideeffects必须和optimize配套使用,但是开启优化其实是有风险的,官方已经明确说明

# Optimizations: If you don't want to optimize, use the proguard-android.txt configuration file
# instead of this one, which turns off the optimization flags.
# Adding optimization introduces certain risks, since for example not all optimizations performed by
# ProGuard works on all versions of Dalvik.  The following flags turn off various optimizations
# known to have issues, but the list may not be complete or up to date. (The "arithmetic"
# optimization can be used if you are only targeting Android 2.0 or later.)  Make sure you test
# thoroughly if you go this route.

综上一方面开启后不一定能清除掉日志,另一方面还会有一定风险,所以个人感觉这个混淆命令了解下就好了,使用到项目中还需谨慎。

查看混淆后的崩溃日志

 

 

代码经过混淆打包后会在studio中生成一个mapping.txt文件,这个文件很重要,会将混淆后的字符和原代码对应起来,打开文件可以看下里面的内容,可以说巨长无比,动辄10多万行!!,不过不要紧我们并不需要了解里面的内容,这个文件是在程序崩溃查找定位的时候使用到的。还是以上图为例

QQ截图20180926100635.png

 

混淆后我们只能看到这些无意义的api方法,如何转换成真正的代码,使用retrace命令

retrace.bat mapping.txt bug.txt

一行命令搞定,retrace.bat在sdk/tools/progurad/bin目录下,其中bug.txt就是原来被混淆过的崩溃日志信息。输出信息如下

 

QQ截图20180926111626.png

 

可以看到原来无意义的方法名都被转换成了具体名字。

keep注解

个人感觉非常好用的一个注解,使用该注解最大的一个好处就是你不用关心混淆的语法,真正做到了哪里混淆语法不会keep哪里,简单方便,该注解作用范围广,类名,成员变量名,方法名都可以keep住,很多人都会使用这个注解,但关于原理可能有些人就不太知道,其实答案都在默认的混淆文件中

-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep (...);
}

google已经替我们写好了混淆规则,所以这就是为什么使用keep注解之后不会被混淆的原因。

小总结

总体来说keep还是非常好用的,但有时还是需要和混淆语法结合使用,举个最简单的例子,bean不能被混淆这是大家都知道的使用,但是往往一个模块中会包括大量的bean类,如果使用keep注释的话需要给每一个bean类都加上注解,这里有一个比较坑的地方,虽然keep注释可以写在类上面keep掉类名不被混淆,但是内部类还是会被混淆掉的,这个有点奇怪因为
···
-keep @android.support.annotation.Keep class * { ;}
···
已经明确写明只要写在类上面就可以全部keep掉该类才对,但是自己实测发现并不能。也就是说如果bean类比较复杂嵌套了几个内部类,那么需要给内部类都写上keep,漏掉其中一个可能就导致崩溃问题。这也就是我说的keep注释还是需要和混淆语法结合使用,keep掉bean类不被混淆的语法很简单比如
···
keep class xxx.xxx.
{ ;}
···
一句混淆语法就能少写很多的keep,还不会出错。所以是使用keep还是混淆语法要看具体使用场景来定。

默认混淆文件

可能很多人会觉得默认混淆文件就是

 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

指定的proguard-android.txt,但是如果你有点好奇心,去研究下混淆规则,你就会发现问题有点蹊跷,为什么这么说呢,默认情况下关于四大组件的混淆规则是默认生成的并不需要我们主要写一些keep语法去避免四大组件的混淆问题,那么问题来了这些混淆规则到底写在哪里,如果觉得是写在proguard-android.txt那肯定是不正确的,可以打开该文件去看下里面默认的混淆规则你就会发现关于activity的混淆只有如下语句

-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

熟悉混淆语法的话应该都知道该混淆规则是无法keep住类名的,但是事实情况却是运行release包后不会发生崩溃,那只能说明activity的混淆是在另外的地方被悄悄处理掉了,事实上在proguard-android.txt混淆文件在gradle2.2之后就被放弃使用了,gradle在运行后会生成一个混淆文件。

Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
will be ignored by new version of the Android plugin for Gradle.

 

 

具体的路径可以在

QQ截图20181026155745.png

中找到,每次去编译运行的时候gradle都会根据manifest中的组件去动态生成混淆规则,所以这个文件才可以说是默认的混淆文件。

小疑问

事实上这里我自己是有一个困惑的,首先一个问题就是,在gradle中配置混淆时

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

getDefaultProguardFile('proguard-android.txt')这个一定要有吗,既然如今gradle可以自动生成一个混淆文件,那么getDefaultProguardFile('proguard-android.txt')也就是过去时了,完全可以不配置,网上很多文章感觉都有些问题都会配置这个路径,实际情况就是你去掉getDefaultProguardFile('proguard-android.txt')之后,不该混淆的文件照样不会混淆,完全没问题。那么自己的一个疑惑点就是'proguard-android.txt中有关于keep注解的keep规则,如果'proguard-android.txt'都不配置了,那么keep注释还能生效吗,实测后发现居然可以生效,并且在appt_rule.txt混淆文件中也没有发现关于keep注释的信息,那最终keep注释的keep规则被放到哪里,自己的一个猜测就是gradle脚本迷之处理掉了,就不纠结这个问题了。如果有人知道原因希望留言告知一下

一个混淆的小发现

这个发现总结起来就是:module A依赖module B,module A实现了module B的一个接口C,接口C中的方法都被keep住了,那么module A中实现该接口的类,它里面对应C接口的方法都不会被混淆掉

多模块之间混淆问题

这个才是文章重点吧,上面都是自己在使用混淆过程中了解到的一些问题。混淆代码可以分开两类,这里说的两类的一个前提就是组件化开发的形式,如果是传统开发模式,所有业务逻辑都在主工程中那么混淆规则也就只能全部写在一个混淆文件中,那就没什么好说的了。当以组件化开发的形式进行混淆时,最终各个模块是以依赖的形式添加到主module当中,举个简单的例子如图

 

QQ截图20180926174400.png

 

 

app是主module,xsb_vip是一个业务module,在平时开发中app以

QQ截图20180926174703.png


implementation project形式依赖,等到最终上线是改成从本地仓库获取的形式,那么问题来了xsb_vip模块的混淆规则写在哪里,是写在app模块中还是vip模块。这也就是我上面所说的混淆代码在组件化开发中分为两类。

app模块编写混淆

这种形式比较简单无脑,全部的混淆规则都写在了app模块里面,如果app模块依赖很多个业务模块那么app模块中的混淆规则将会非常的庞大,不利于代码的维护,使用app模块编写所有混淆命令是基于业务模块当中不再编写混淆命令为前提,所以在打包将业务模块上传到私有仓库时,业务模块都是不开启混淆功能的!!也就是minifyEnabled都是false,如果强行将业务模块的minifyEnabled设置为true会有什么问题,那么程序将很大概率会崩溃掉,因为开启minifyEnabled后app模块的混淆规则将无法作用到业务模块上,导致业务模块不该混淆的代码被混淆从而导致崩溃。minifyEnabled设置为false就会保证app模块的混淆规则作用到业务模块上去,这也是minifyEnabled设置为true还是false对组件化开发影响比较大的一个地方

但是但是

上述结论都是建立在以implementation或者api形式依赖的前提下,开发阶段我们是以

implementation project(':xsb_vip')

这种形式进行依赖的,你会发现当以这种形式进行依赖时,不管业务模块minifyEnabled是true还是false,只要app模块写上了正确的混淆规则那么程序都能正常运行!

各个模块单独编写混淆规则

这是组件化开发最推荐的做法,最大的优点就是不用在app模块写上大量的混淆规则,只需要在相应模块写特地的混淆,方便了混淆的维护工作。模块当中的gradle混淆配置可以非常的精简

release {
            consumerProguardFiles 'proguard-rules.pro'
        }

只需要配置一行即可,proguard-rules.pro就是该模块特定的混淆规则,使用这种配置最大的一个好处就是业务模块的是否混淆完全由app模块来决定,这种配置有一个非常重要的关键点就是不能设置minifyEnabled true,因为设置为true之后你将会发现业务模块是否混淆的控制权将只能由该模块自身决定,app模块将无法控制业务模块的混淆与否而且设置为true之后还必须额外配置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'该语句,相比较

release {
            consumerProguardFiles 'proguard-rules.pro'
        }

这种简洁的形式,使用minifyEnabled true的配置显得有些冗余以及不灵活。

一些其他诡异的问题

这是自己在测试混淆的时候遇到的奇奇怪怪的问题,以上述业务模块为例将release中的配置修改成

release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            consumerProguardFiles 'proguard-rules.pro'
        }

最终打出来的aar肯定是经过代码混淆处理的,如图所示

 

QQ截图20180927103324.png

 

但是比较奇怪的是activity中的类,照理来说除了混淆文件默认配置的activity keep规则外其他内容都应该被混淆才对(业务模块中并没有额外对activity进行过其他keep处理),然而事实上点击进去会发现里面代码没有经过任何的混淆处理,其他地方的混淆都没有问题唯独activity中的类不会被混淆处理,这看起来是个非常奇怪的地方。然而最终我反复测试得出的结果是这非常大概率就是一个studio的bug,要想验证也非常简单,打出一个apk然后反编译下,找到对应的activity之后你可以看到其实是已经经过混淆处理的。

以上就是自己在开发过程中实际遇到的一些关于混淆的问题,前前后后花了一些时间总结整理,混淆本身就语法而言是不难的,但比较另人费解的地方是当它和gradle一起作用的时候有时候这个规则有时候就令人难以捉摸

你可能感兴趣的:(Android,android)