• How To Use Proguard in Android APP


      在Android开发完成即将发布给用户使用时,还有最后重要的一步:代码混淆,这时候,Proguard就派上用场了,大家谁也不想辛辛苦苦写的代码太容易被别人反编译过来,而Proguard就是帮我们实现这一目的的工具。关于Proguard是什么,有什么特点,可以在这个链接了解:http://proguard.sourceforge.net/,简单来说,Proguard有如下几大功能:

    •   Shink :  扫描并移除代码中无用的类、属性字段、方法                         (第一步)
    • Optomize :  对字节码进行优化,移除无用的指令                                        (第二步)
    • Obfuscate:  使用简短、无意义的字母替换类名、属性字段名等,达到混淆目的    (第三步)
    • Preverify  :  上面三步完成后,对其进行预校验                                           (第四步)

      流程如下图所示:

      

      从刚刚网站链接可以看到,Proguard可以对Java代码进行混淆从而达到基本的反编译、逆向工程保护目的,另外还有一个针对Android的商业扩展版工具: DexGuard,对于APP开发来说,可以进一步对代码进行保护,例如:加密字符串、资源文件、本地库等,而这些是Proguard缺少的。这里首先介绍Android Studio内置支持的Proguard。

      示例代码GitHub上:https://github.com/linjk/TestProguard.git

      新建一个Android工程后,Android Studio默认帮我们新建了一个混淆文件,在release配置里默认没使能混淆,如下图所示:

      

      在未进行混淆前,先生成一个发布版本的app,用于和混淆后的app进行对比,命名为:TestProguard-unproguard.apk

      下面开始进行代码混淆:

      1. 修改app/build.gradle文件下buildTypes的release使用混淆功能,即:"minifyEnabled true"

      2. 编辑proguard-rules.pro文件,编写混淆规则:

        2.1 通用混淆规则

     1 #代码的压缩级别, 0~7之间,默认为5
     2 -optimizationpasses 5
     3 #包名不混合大小写,混淆后的类名为小写(主要针对windows用户的配置)
     4 -dontusemixedcaseclassnames
     5 #不去忽略非公共的库类
     6 -dontskipnonpubliclibraryclasses
     7 #不去忽略非公共的库的类的成员
     8 -dontskipnonpubliclibraryclassmembers
     9 #优化 不优化输入的类文件
    10 -dontoptimize
    11 #不做预校验(Android不需要此步骤,跳过可加快混淆速度)
    12 -dontpreverify
    13 #混淆后生成映射文件-->(包含有类名->混淆后类名的映射关系)
    14 -verbose
    15 #指定映射文件的名称
    16 -printmapping proguardMapping.txt
    17 #-applymapping proguardMapping.txt
    18 
    19 #混淆时所采用的算法,谷歌推荐,一般不改变
    20 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
    21 #保护代码中的Annotation不被混淆,在使用如fastJSON时的实体映射
    22 -keepattributes *Annotation*
    23 #避免混淆泛型,在使用如fastJSON时的实体映射
    24 -keepattributes Signature
    25 #抛出异常时保留代码行号
    26 -keepattributes SourceFile,LineNumberTable
    27 #忽略警告  ----- 不推荐全部忽略警告,如果确认哪些类和app无关,可以使用 -dontwarn org.apache.http.**来忽略
    28 -ignorewarning
    29 #记录生成的日志数据,gradle build时在本项目根目录输出
    30 #apk 包内所有 class 的内部结构
    31 -dump class_files.txt
    32 #未混淆的类和成员
    33 -printseeds seeds.txt
    34 #列出从 apk 中删除的代码
    35 -printusage unused.txt
    36 
    37 ################# Android默认的常用类保留不混淆 ######################
    38 #保留所有的本地native方法不被混淆
    39 -keepclasseswithmembernames class * {
    40     native <methods>;
    41 }
    42 #保持哪些类不被混淆
    43 -keep public class * extends android.app.Fragment
    #所有Activity子类不被混淆
    44 -keep public class * extends android.app.Activity 45 -keep public class * extends android.app.Application 46 -keep public class * extends android.app.Service 47 -keep public class * extends android.content.BroadcastReceiver 48 -keep public class * extends android.content.ContentProvider 49 -keep public class * extends android.app.backup.BackupAgentHelper 50 -keep public class * extends android.preference.Preference 51 -keep public class * extends android.view.View
    52 53 -keep public class com.android.vending.licensing.ILicensingService 54 #如果有引用v4包,添加下面这行 55 -keep public class * extends android.support.v4.app.Fragment.** {*;} 56 #保留在Activity中的方法参数是view的方法,从而在layout里编写的onClick不会被影响 57 -keepclassmembers class * extends android.app.Activity{ 58 public void *(android.view.View); 59 } 60 #枚举类不能被混淆 61 -keepclassmembers enum * { 62 public static **[] values(); 63 public static ** valueOf(java.lang.String); 64 } 65 #保留自定义控件(继承自view)不被混淆 66 -keep public class * extends android.view.View{ 67 ** get*(); 68 void set*(***); 69 public <init>(android.content.Context); 70 public <init>(android.content.Context, android.util,AttributeSet); 71 public <init>(android.content.Context, android.util,AttributeSet, int); 72 } 73 74 #保留Parcelable序列化的类不被混淆 75 -keep class * implements android.os.Parcelable{ 76 public static final android.os.Parcelable$Creator *; 77 } 78 79 #保留Serialable序列化的类不被混淆 80 -keepclassmembers class * implements java.io.Serializable{ 81 static final long serialVersionUID; 82 private static final java.io.ObjectStreamField[] serialPersistenFields; 83 private void writeObject(java.io.ObjectOutputStream); 84 private void readObject(java.io.ObjectInputStream); 85 java.lang.Object writeReplace(); 86 java.lang.Object readResolve(); 87 } 88 89 #对于R(资源)下的所有类机器方法不能被混淆 90 -keep class **.R$* { 91 *; 92 } 93 94 #对于带有回调函数onXXEvent的方法不能被混淆 95 -keepclassmembers class * { 96 void *(**On*Event); 97 }

          这部分是所有APP项目都通用的混淆规则,可以直接Ctrl+c, Ctrl+v来使用。

        2.2 针对特定APP的混淆规则

          每个APP工程都会按模块来分包开发,现在用来测试的工程只有一个默认的Activivy类,就会简单很多,但是还是有很通用的模块混淆规则的,如下:

          

     1 #1. 保护实体类和成员
     2 -keep public class cn.linjk.testproguard.beans.** {
     3     public void set*(***);
     4     public *** get*();
     5     public *** is*();
     6 }
     7 
     8 #2. 很重要,内嵌类要保留(建议内嵌类单独文件处理,以免忘记导致后面调试查找麻烦)
     9 -keep class cn.linjk.testproguard.activity.IndicatorActivity$*{
    10     *;
    11 }
    12 -keep public class * extends android.support.v7.app.AppCompatActivity
    13 
    14 #3. 数据库驱动不被混淆
    15 #-keep class cn.linjk.testproguard.database.** {*;}
    16 
    17 #3. 对webView的处理
    18 -keepclassmembers class * extends anroid.webkit.webViewClient {
    19     public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    20     public boolean *(android.webkit.WebView, java.lang.String);
    21 }
    22 
    23 -keepclassmembers class * extends android.webkit.webViewClient {
    24     public void *(android.webkit.webView, java.lang.String);
    25 }
    26 
    27 # 4. 网络请求包不被混淆
    28 -keep class cn.linjk.testproguard.networkservice.** {*;}
    29 
    30 # 5. adapter不被混淆
    31 -keep class cn.linjk.testproguard.adapter.** {*;}
    32 
    33 #6. 第三方SDK的混淆处理(第三方SDK包一般都是经过混淆了,这里是避免这些SDK的类和方法在该app中被混淆,在sdk官网都会列出混淆规则,复制粘贴就行了,*.so文件不必理会)
    34 # butterknife
    35 -keep class butterknife.** {*;}
    36 -keep public class cn.jpush.** {*;}
    37 -keep public class com.google.** {*;}
    38 -keep public class com.readystatesoftware.viewbadger.** {*;}
    39 
    40 -keep public class com.alipay.** {*;}
    41 -dontwarn com.alipay.android.app.**
    42 
    43 -keep public class com.mob.commons.** {*;}
    44 -keep public class com.mob.tools.** {*;}
    45 
    46 #7. layout资源目录下自定义view不被混淆
    47 -keep public class cn.linjk.testproguard.view.** {*;}

    #8. JavaScript调用的java原生方法不被混淆
    -keepclassmembers class cn.linjk.testproguard$JSCallInterfaces {
    <methods>;
    }
    #9. 反射类处理,如*.class.getField()/getMethod()等方法

      这两部分规则复制到proguard-rules.pro,然后发布即可进行混淆。

      经过混淆后生成的apk,命名为:TestProguard-proguard.apk,两个apk文件对比如下:

      

      可以发现,经过混淆后的apk包体积更小。

      现在来看看混淆后反编译后的文件:

      

      因为这个测试工程没有什么类,用的另外一个项目的app来进行反编译。可以看到,这里的类用了a、b、c等字母来替代了类名和方法,进一步可以在smali文件看到:

      

      这样,别人就算反编译了,也不知调用的是什么方法,那我们如何知道呢,在生成发布包后,在app/build/output/release/mapping生成了输出文件,包括哪些类哪些方法被混淆了,哪些类哪些方法被留下了,都有对应说明,我们在查看线上bug时,就要靠它们来找到对应的应用奔溃地方来进行bug修复了。生成文件如下图:

      

      最后,要补充几点重要的:

      1. 混淆后的app必须要进行monkey测试,保证每个页面都路由到,看是否会产生奔溃,然后,在进行重要功能测试,如:推送、支付、扫描、定位等。

      2. 多模块的工程,在库模块也需要进行混淆,然后在app主工程保留库的类和方法。

      

    GitHub: https://github.com/linjk LinJK: https://www.linjk.cn:3298
  • 相关阅读:
    MySQL存储过程札记
    一个关于 Linux环境下输出操作符 >和>>的问题
    记一次dubbo服务丢失的问题排查
    JDK1.8中的时间处理API
    Redis的懒惰删除与对象共享
    Java内存模型与线程
    Java hotspot即时编译和提前编译
    Java线程安全与锁优化
    Java编译器优化技术
    自动清理SQL Server的数据库日志
  • 原文地址:https://www.cnblogs.com/linjk/p/6023032.html
Copyright © 2020-2023  润新知