项目实践--混淆【详解】

来源:互联网 发布:淘宝pc链接转换无线 编辑:程序博客网 时间:2024/06/06 19:59

1.写在前面

我花了两三天时间来弄当前我公司开发的这个项目,其实我一直在想为什么要混淆这个项目。不混淆的原因有很多:
1.反正小项目,就算被反编译了又怎样,无所谓,再说混淆了,别人想反编译花点时间照样轻松破解。
2.混淆后测试是个大问题,我们给测试或者上线的项目一定是经过混淆的项目,但是平时开发开启混淆调试代价太大了,太浪费时间了,所以有时候我们在debug版本下测试完成没问题,结果在release情况下一堆的问题。
3.现在一堆的第三方加固,阿里聚安全(用的比较多),360,应用宝等等,所以感觉也够了。阿里聚安全我用的比较多,今天5月份时,加固后我用工具(dex2jar)反编译后很多代码是乱码的,差不多达到混淆的效果,最关键的可以减少1M多的APK大小,感觉太神奇了。这次我试了下APK大小减少了几百K,但是反编译出来的代码并没有乱码,真心不知道什么情况,其余两个没试过。但是用他们的在上线时有一堆的问题,比如我用阿里的加固,然后去应用宝上线,结果把apk提交上去时会提示你安装包有问题,推荐你使用它的工具加固,不然审核可能过不了,我用应用宝的工具加固完后去360上线,提交安装包又会提示安装包有问题,提示使用它的工具加固….
所以比较奇葩,我觉得第三方加固只是锦上添花,根本我们还是需要混淆项目。

2.必须混淆

我的项目混淆成功后,从4.4M变成3.3M,我就知道搞了这两天的混淆是值得的。混淆总结起来主要有两个难点:
1.开启混淆打包签名出来,能不能build successful。因为如果混淆文件没有配置好,经常这一步就堵死了很多人……
2.可以build successful,拿到混淆后的apk,但是运行起来可能会出现崩溃或者不正确的地方。
3. 我现在这个项目才迭代两个版本,这次混淆已经有点累,很多地方可以build successful出来,但是总是测试有bug,所以混淆最好越早越好。

一般只要完成这两步,混淆基本没问题了。对于第一步,我觉得要学习混淆100% 要学习郭神的这篇博客,写的太好了。
一定要看

A 开启混淆

这里比较简单,主要我们竟然开启混淆了,那一定也要配置全,不然浪费了。 shrinkResources true 行代码也可以上去,因为我们实际项目中坑定不止一个app module,还有其他很多lib module,比如看我的
这里写图片描述
虽然开启混淆后,gradle编译时会自动将其他库没有用到的方法 属性移除(所以apk安装包才瘦身),但是对于资源可不会进行这一步,一般项目中算资源的有.png,.xml,所以对于这些module中的无用资源我们可以通过这行代码来移除。
但是需要特别注意的是,这行代码必须在开启混淆后,也就是说minifyEnabled true时才起作用,不然我们单独写上哪一行代码也是没用的

     release {            //混淆            minifyEnabled true            //Zipalign优化            zipAlignEnabled true            // 移除无用的resource文件            shrinkResources true            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'            signingConfig signingConfigs.release            //apk命名  ksolar-1-1.0.0-20171010.apk            android.applicationVariants.all { variant ->                variant.outputs.each { output ->                    def outputFile = output.outputFile                    if (outputFile != null && outputFile.name.endsWith('.apk')) {                        //这里修改apk文件名                        def fileName = "ksolar-${defaultConfig.versionCode}-${defaultConfig.versionName}-${releaseTime()}.apk"                        output.outputFile = new File(outputFile.parent, fileName)                    }                }            }        }

B 配置混淆文件

如果有多个module,不用每个module都去build.gradle中开启混淆,只要在主app module配置就可以了,其他如果是依赖关系,也会被混淆的,我反编译看过。
如果看过郭神的那篇博客,我们知道sdk里面有个混淆文件,但是不够,我们也需要配置自己的混淆文件,但是估计最后混淆的效果是两个文件共同的作用的。
很多博客把混淆文件的内容分为几部分,我这里代码也一样分为三部分。1.基本不动区域(基本指令、默认保留),可以直接复制。
2.第三方库区域。虽然每个人使用的第三库都不一样,但是其实这里不难,很多第三方lib都提供了混淆规则,使用直接复制就可以了,不行的话,build的时候也会通不过提示,把提示的内容复制谷歌或者stackoverflow很容易就知道,我这里说的是依赖的形式,对于一些jar包,因为我这项目没用到,所以没写。
3.定制化区域。最后就是每个人项目情况不一样,写的形式可能不同。
这里面的代码确实是可以直接复制到项目中,

#---------------------------------基本指令区----------------------------------#代码混淆压缩比,在0和7之间,默认为5,一般不需要改-optimizationpasses 5#混淆时不使用大小写混合,混淆后的类名为小写-dontusemixedcaseclassnames#指定不去忽略非公共的库的类-dontskipnonpubliclibraryclasses#指定不去忽略非公共的库的类的成员-dontskipnonpubliclibraryclassmembers#不做预校验,preverify是proguard的4个步骤之一,Android不需要preverify,去掉这一步可加快混淆速度-dontpreverify#有了verbose这句话,混淆后就会生成映射文件,包含有类名->混淆后类名的映射关系,然后使用printmapping指定映射文件的名称-verbose-printmapping proguardMapping.txt#指定混淆时采用的算法,后面的参数是一个过滤器,这个过滤器是谷歌推荐的算法,一般不改变-optimizations !code/simplification/arithmetic, !field/*, !class/merging/*#保护代码中的Annotation不被混淆,表示对注解中的参数进行保留,这在JSON实体映射时非常重要,比如fastJson-keepattributes *Annotation*#避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson-keepattributes Signature#抛出异常时保留代码行号-keepattributes SourceFile, LineNumberTable#表示不混淆任何包含native方法的类的类名以及native方法名-keepclasseswithmembernames class * {    native <methods>;}

其实这里里面很多代码和sdk里面的混淆文件代码一样的,但是最后测试还是没问题,所以我也没把这里代码删去了。下面这是默认保留区,项目中都是通用的,代码注释很详细,确实是这样的

#---------------------------------默认保留区---------------------------------#保留了继承自Activity、Application这些类的子类,因为这些子类,都有可能被外部调用#比如说,第一行就保证了所有Activity的子类不要被混淆-keep public class * extends android.app.Activity-keep public class * extends android.app.Application-keep public class * extends android.app.Service-keep public class * extends android.content.BroadcastReceiver-keep public class * extends android.content.ContentProvider-keep public class * extends android.preference.Preference-keep public class com.android.vending.licensing.ILicensingService-keep public class * extends android.app.backup.BackupAgentHelper-keep public class * extends android.preference.Preference# 如果有引用v4包可以添加下面这行-keep public class * extends android.support.v4.app.Fragment#如果引用了v4或者v7包,下面这行表示对android.support包下的代码不警告-dontwarn android.support.**#表示不混淆任何包含native方法的类的类名以及native方法名(http://blog.csdn.net/guolin_blog/article/details/50451259)-keepclasseswithmembernames class * {    native <methods>;}#保留在Activity中的方法参数是view的方法,从而我们在layout里面编写onClick就不会被影响-keepclassmembers class * extends android.app.Activity {    public void * (android.view.View);}#枚举类不能被混淆-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);}#保留自定义控件(继承自View)不被混淆-keep public class * extends android.view.View {    *** get*();    void set*(***);    public <init>(android.content.Context);    public <init>(android.content.Context, android.util.AttributeSet);    public <init>(android.content.Context, android.util.AttributeSet, int);}#保留Parcelable序列化的类不被混淆(表示不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败)-keep class * implements android.os.Parcelable {    public static final android.os.Parcelable$Creator *;}#保留Serializable序列化的类不被混淆-keepclassmembers class * implements java.io.Serializable {    static final long serialVersionUID;    private static final java.io.ObjectStreamField[] serialPersistentFields;    private void writeObject(java.io.ObjectOutputStream);    private void readObject(java.io.ObjectInputStream);    java.lang.Object writeReplace();    java.lang.Object readResolve();}#对于R(资源)下的所有类及其方法,都不能被混淆-keep class **.R$* {    *;}#对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆-keepclassmembers class * {    void * (**On*Event);    void *(**On*Listener);}#有用到WEBView的JS调用接口不被混淆(本项目暂时没使用,注释)#-keepclassmembers class fqcn.of.javascript.interface.for.Webview {#   public *;#}-keepclassmembers class * extends android.webkit.WebViewClient {    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);    public boolean *(android.webkit.WebView, java.lang.String);}-keepclassmembers class * extends android.webkit.WebViewClient {    public void *(android.webkit.WebView, jav.lang.String);}#----------------------------------------------------------------------------#忽略警告-dontwarn com.parse.**

这第三方lib,因为我看了下,很多不同的版本貌似有不同的混淆规则,比如我是用的retrofit是2.2.0的,现在的是2.3.0里面提供的规则已经不一样了。同样的还有rxjava okhttp,所以这里需要自己去找自己的。我也提供我的几个吧,到时候不行可以试一试我的。。。。

#rxjava-dontwarn sun.misc.**-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { long producerIndex; long consumerIndex;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode;}
#glide-keep public class * implements com.bumptech.glide.module.GlideModule-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {  **[] $VALUES;  public *;}
#butterknife-dontwarn butterknife.internal.**-keep class butterknife.** { *; }-keep class **$$ViewBinder { *; }-keepclasseswithmembernames class * {    @butterknife.* <fields>;}-keepclasseswithmembernames class * {    @butterknife.* <methods>;}
#fastjson-dontwarn com.alibaba.fastjson.**-keep public class com.alibaba.fastjson.**{*;}#gson-dontwarn com.google.**-keep class com.google.gson.** {*;}-keep class sun.misc.Unsafe { *; }-keep class com.google.gson.stream.** { *; }
#mpandroidchart-dontwarn io.realm.**-keep class com.github.mikephil.charting.** { *; }#zxing-keep class com.google.zxing.** { *; }-keepclassmembers class com.google.zxing.** {*;}

对于最后的定制化区域,我们主要是保护一些代码不让他们被混淆,导致测试报错。主要要对这几个进行定制化,bean,内部类、工具类。bean看自己项目怎么放的,如果全部在一个包,那直接使用如下代码就可以了

-keep class 自己bean的包名.** { *; }

比如所有的bean都在allbeans包里面,并且路径是com.test.allbeans
那么代码如下

-keep class com.test.allbeans.** { *; }

如果是分开放的,比如和我的一样,那就要麻烦点,每个都保护到,不然他们被混淆

-keep class com.kstar.device.ui.alarm.bean.** { *; }-keep class com.kstar.device.ui.device.bean.** { *; }-keep class com.kstar.device.ui.home.bean.** { *; }-keep class com.kstar.device.ui.list.bean.** { *; }-keep class com.kstar.device.ui.login.bean.** { *; }-keep class com.kstar.device.ui.station.bean.** { *; }-keep class com.kstar.device.ui.mine.bean.** { *; }

先说工具类,一般我们如果不特意说明,绝对会被混淆的,但是我试了最好保护它,因为一些工具类可能存在反射代码,而且这中代码好像也没什么混淆价值,再说也好保护,都是在一个包中,直接keep就可以了,比如我的.

##对commonutils报下的代码不警告-dontwarn kstar.mycommon.commonutils.**-keep class kstar.mycommon.commonutils.** { *; }

最后就是内部类,我用了将近三天混淆的原因这个耽误了我半天时间,网上很多地方有关内部类的代码都是如下

-keep class com.manjay.housebox.activity.CityListActivity$*{        <fields>;        <methods>;}-keepclassmembers class com.manjay.housebox.activity.CityListActivity$*{*;}-keep class com.manjay.housebox.map.MapActivity$*{        <fields>;        <methods>;}-keepclassmembers class com.manjay.housebox.map.MapActivity$*{*;}

我也不知道从哪里传出来的,既然与class有关,那么每个人绝对不一样,怎么会就被定死呢,没有点说明,而且都是这样的,最开始housebos误导我了,这翻译过来好像有点内部类的意思…..但其实不是这样的,并且改成自己内部类的包名好像也是不对的。还好找到这篇博客内部类混淆
他的方法是有用的,比如我的RegPowerFragment中有一个内部类RigisterParam,那么我的代码就是

# 内部类混淆配置-keepattributes Exceptions,InnerClasses,...-keep class com.kstar.device.ui.login.fragment.RegPowerFragment$RigisterParam {    *;}

3.完

最后的全部代码就不贴出来了,因为我的项目有一个地方和平常的项目不一样 ,有一个地方需要特别的定制,不然测试出来有问题。这很正常混淆要特别情况特别分析,每个人可能都不一样,有自己代码特殊的地方。但是我觉得主要思路是保护一些不被混淆,不然很容易出问题。
希望大家成功吧……

原创粉丝点击