Android 混淆:proguard实践

前言

网上关于使用 proguard 进行混淆的文章很多,但大部分是从讲解 proguard 知识点这个角度去写的,为什么要混淆,如何去混淆,混淆的注意点等重要的问题反而都没有写。
所以想通过这篇文章来记录我学习 proguard 的过程,一来是整理 android 混淆相关的知识点,二来是总结此次学习混淆的方法,提高学习能力。

proguard 实践

学习任何技术之前应该要有一个明确的目标,本次学习混淆的目标就是会使用 android studio 自带的 proguard 对 APP 项目进行混淆,混淆后的 APP 要能正常运行。
本次学习尝试采用“三段式理论”,三段式即提出问题,分析问题,解决问题

提出问题

  • APP 为什么要进行混淆?
    混淆的首要目的是保护 APP 项目的代码,使 APP 更难被破解;其次是优化 APP,去除无用代码和资源,减小 APP 的大小。
  • 为什么使用 proguard 进行混淆?
    因为 proguard 是一个很优秀的开源混淆代码项目,而且是 google 的 android studio 默认支持的混淆插件。
  • 怎么使用 proguard 进行混淆?
    这个就是后续要重点要分享和实践的内容了,别着急,往后看
  • 如何验证核心代码是否混淆了?
    我配置了 proguard,用 proguard 打包生成了 APK,那我怎么保证我想要混淆的代码已经混淆了呢。
    其实反过来想就行了,就是通过反编译来验证,本篇将用jadx反编译工具来做。

分析问题

经过前面的分析,我们了解到主要的问题集中在如何混淆和混淆验证两个方面上。曾经有某个伟人说过“实践是检验真理的唯一标准”,不知道怎么混淆,那就用起来试试。
接下来我们将通过去混淆 OkHttpDemo 这个项目来进行实践,源码下载请戳这里

开始混淆

  1. 将项目的build.gradle 中的minifyEnabled 置为true

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 26
        buildToolsVersion "26.0.2"
        defaultConfig {
            applicationId "com.torch.easydev.okhttpdemo"
            minSdkVersion 17
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:26.+'
        testCompile 'junit:junit:4.12'
        compile 'com.squareup.okhttp3:okhttp:3.7.0'
        compile 'com.google.code.gson:gson:2.8.0'
    }
    
    2.使用assembleRealease 打包APK,注意这里不能直接run,默认编译运行的APP为debug版本,是不会进行混淆的。     3. 打包过程会发现报错了,提示okio包下的某个类找不到,以下是编译的详细过程。
    // 此Task为proguard混淆的命令
    :app:transformClassesAndResourcesWithProguardForRelease
    // proguard 版本
    ProGuard, version 5.3.2
    Reading input...
    // 读取的jar,包括libs/以及build.gradle的dependencies里用到的
    Reading program jar [/Users/torch/.android/build-cache/7880ba2afce31630824a0110efd86274bc49bc30/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/e6f71d5707a837d00ed85447f14b707f76ae20f0/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.7.0/6edbebdd8868708db718d15c09c4b28037fd487e/okhttp-3.7.0.jar] (filtered)
    Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.0/c4ba5371a29ac9b2ad6129b1d39ea38750043eff/gson-2.8.0.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/0d42c3690aeb29caef324c71b7fc2421c8de1e5b/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/b9555cd093d474b08b7ea2c011a82c2d4c627b86/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.12.0/3742beff8024d0a0073d284b7c5e4cbf73d99b25/okio-1.12.0.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/cb09c2323f3dd0b058deff4426ce2887c32c5698/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/Library/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/26.0.0-alpha1/support-annotations-26.0.0-alpha1.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/446d05455618d1e0c86af9ae0a41a910627b74e1/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/5a18253d3fd47698167249e46562f1794b833a76/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/0d75c8d0e60a38ec97c92a95e11ba8c609f3847d/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/.android/build-cache/a23ef26dcf19d0326ebc9315975b5fd8c0e068f1/output/jars/classes.jar] (filtered)
    Reading program jar [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/transforms/mergeJavaRes/release/jars/2/1f/main.jar] (filtered)
    Reading program directory [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/classes/release] (filtered)
    Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/android.jar]
    Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/optional/org.apache.http.legacy.jar]
    Note: duplicate definition of library class [org.apache.http.params.HttpConnectionParams]
    Note: duplicate definition of library class [org.apache.http.params.HttpParams]
    Note: duplicate definition of library class [org.apache.http.params.CoreConnectionPNames]
    Note: duplicate definition of library class [org.apache.http.conn.ConnectTimeoutException]
    Note: duplicate definition of library class [org.apache.http.conn.scheme.HostNameResolver]
    Note: duplicate definition of library class [org.apache.http.conn.scheme.SocketFactory]
    Note: duplicate definition of library class [org.apache.http.conn.scheme.LayeredSocketFactory]
    Note: duplicate definition of library class [android.net.http.SslCertificate$DName]
    Note: duplicate definition of library class [android.net.http.SslCertificate]
    Note: duplicate definition of library class [android.net.http.HttpResponseCache]
    Note: duplicate definition of library class [android.net.http.SslError]
    
    // proguard 会优化、去除重复的类
    Note: there were 11 duplicate class definitions.
          (http://proguard.sourceforge.net/manual/troubleshooting.html#duplicateclass)
    
    Initializing...
    
    // 找不到okio包下的三个类
    Warning: okio.DeflaterSink: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
    Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
    Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
    
    // 找不到gson包和okio包下的类
    Note: com.google.gson.internal.UnsafeAllocator: can't find dynamically referenced class sun.misc.Unsafe
    Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class com.android.org.conscrypt.SSLParametersImpl
    Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
    Note: okhttp3.internal.platform.AndroidPlatform$CloseGuard: can't find dynamically referenced class dalvik.system.CloseGuard
    Note: okhttp3.internal.platform.Platform: can't find dynamically referenced class sun.security.ssl.SSLContextImpl
    Note: com.google.gson.internal.UnsafeAllocator accesses a declared field 'theUnsafe' dynamically
    Note: there were 5 unresolved dynamic references to classes or interfaces.
          You should check if you need to specify additional program jars.
          (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass)
    Note: there were 1 accesses to class members by means of introspection.
          You should consider explicitly keeping the mentioned class members
          (using '-keep' or '-keepclassmembers').
          (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclassmember)
    
    Warning: there were 3 unresolved references to classes or interfaces.
             You may need to add missing library jars or update their versions.
             If your code works fine without the missing classes, you can suppress
             the warnings with '-dontwarn' options.
             (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)
    
    Warning: Exception while processing task java.io.IOException: Please correct the above warnings first.
    :app:transformClassesAndResourcesWithProguardForRelease FAILED
    
    FAILURE: Build failed with an exception.
    
    // 执行proguard混淆失败
    * What went wrong:
    Execution failed for task ':app:transformClassesAndResourcesWithProguardForRelease'.
    > Job failed, see logs for details
    
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    
    BUILD FAILED
    
    Total time: 5.691 secs以上是proguard混淆的整个编译过程,发现混淆失败报错了,但明明代码是对的,用debug编译运行都没问题,那是不是混淆的哪个步骤出错了呢? 接下来我们一起来了解一下android studio具体是怎么使用proguard进行编译混淆的。
    proguard 相关知识点
    Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。
    压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)优化(Optimize):对字节码进行优化,移除无用的指令混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的
    混淆就是移除没有用到的代码,然后对代码里面的类、变量、方法重命名为人可读性很差的简短名字。  那么有一个问题,ProGuard怎么知道这个代码没有被用到呢?  这里引入一个Entry Point(入口点)概念,Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。  那么这个入口点怎么来呢?就是从ProGuard的配置文件来,只要有这个配置了,那么就不会被移除。
     buildTypes {
            release {
                minifyEnabled true
                # 这里的proguard-android.txt以及proguard-rules.pro就是入口点的配置文件
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }proguard-android.txt文件在Android_SDK_Home/tools/proguard 文件夹下,是google官方提供的一个android app项目的默认通用配置文件,适用于所有android app项目。而proguard-rules.pro默认为空,可以在这里放置一些和特定app相关的配置。
    ## proguard-rules.pro文件
    # 本文件用于添加应用特有混淆配置信息,这些信息会添加在默认配置信息中,作为入口点的判断依据
    # Add project specific ProGuard rules here.
    # By default, the flags in this file are appended to flags specified
    # in E:\android\android-sdk/tools/proguard/proguard-android.txt
    # You can edit the include path and order by changing the proguardFiles
    # directive in build.gradle.
    #
    # For more details, see
    #   http://developer.android.com/guide/developing/tools/proguard.html
    
    # Add any project specific keep options here:
    
    # If your project uses WebView with JS, uncomment the following
    # and specify the fully qualified class name to the JavaScript interface
    # class:
    #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    #   public *;
    #} 接下来我们来一起看一下proguard-android.txt里面有哪些东西,默认哪些东西不能被混淆,了解了这些,我们对如何编译自定义的proguard-rules就能有一个大致的理解了。
    # This is a configuration file for ProGuard.
    # http://proguard.sourceforge.net/index.html#manual/usage.html
    #
    # This file is no longer maintained and is not used by new (2.2+) versions of the
    # Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
    # default rules at build time and stores them in the build directory.
    
    # 混淆时不使用大小写混合,混淆后的类名为小写
    -dontusemixedcaseclassnames
    
    # 指定不去忽略非公共的库的类
    -dontskipnonpubliclibraryclasses
    
    # 有了verbose这句话,混淆后就会生成映射文件
    # 包含有类名->混淆后类名的映射关系
    # 然后可以使用printmapping指定映射文件的名称
    -verbose
    
    # Optimization is turned off by default. Dex does not like code run
    # through the ProGuard optimize and preverify steps (and performs some
    # of these optimizations on its own).
    # 不启用优化,建议使用此选项
    # proguard的优化选项和java虚拟机中的字节码dex优化有冲突,可能会产生一些未知的问题
    -dontoptimize
    
    # 不做预校验,preverify是proguard的4个步骤之一
    # Android不需要preverify,去掉这一步可加快混淆速度
    -dontpreverify
    
    # Note that if you want to enable optimization, you cannot just
    # include optimization flags in your own project configuration file;
    # instead you will need to point to the
    # "proguard-android-optimize.txt" file instead of this one from your
    # project.properties file.
    
    # 保留注解,因为注解是通过反射机制来实现的
    -keepattributes *Annotation*
    
    # 保留google的授权服务
    -keep public class com.google.vending.licensing.ILicensingService
    -keep public class com.android.vending.licensing.ILicensingService
    
    # 保留本地NDK方法
    # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    # 保留View中的set和get方法
    # keep setters in Views so that animations can still work.
    # see http://proguard.sourceforge.net/manual/examples.html#beans
    -keepclassmembers public class * extends android.view.View {
       void set*(***);
       *** get*();
    }
    
    # 保留Activity当中的View相关方法
    # We want to keep methods in Activity that could be used in the XML attribute onClick
    -keepclassmembers class * extends android.app.Activity {
       public void *(android.view.View);
    }
    
    # 保留枚举类
    # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    # 保留序列号相关类即方法
    -keepclassmembers class * implements android.os.Parcelable {
      public static final android.os.Parcelable$Creator CREATOR;
    }
    
    # 保留资源应用名
    -keepclassmembers class **.R$* {
        public static <fields>;
    }
    
    
    # The support library contains references to newer platform versions.
    # Don't warn about those in case this app is linking against an older
    # platform version.  We know about them, and they are safe.
    # 忽略support包下的警告
    -dontwarn android.support.**
    
    # Understand the @Keep support annotation.
    # 保留support包下的动画
    -keep class android.support.annotation.Keep
    
    -keep @android.support.annotation.Keep class * {*;}
    
    -keepclasseswithmembers class * {
        @android.support.annotation.Keep <methods>;
    }
    
    -keepclasseswithmembers class * {
        @android.support.annotation.Keep <fields>;
    }
    
    -keepclasseswithmembers class * {
        @android.support.annotation.Keep <init>(...);
    }
    
    可以看到,这里主要保留的是一些不能被混淆的类、方法或者变量名,编写配置文件的核心思想就是尽可能地去做混淆,除非有不得不混淆的理由。 那哪些东西是不能做混淆的呢? 以下列举一些常用的不能被混淆的情况:
    本地方法:因为本地方法是根据方法名去调用的,若混淆后会导致找不到此方法名。反射相关的方法和类:反射原理就是通过方法名和类名去实例化相应的对象,调用相关的方法,当然也不能混淆。setXX和getXX方法:这里指的是通过配置文件直接生成相应的set和get方法的相关库,所以javabean类很多情况下不能做混淆。第三方jar包:这个需要具体情况具体分析,很多知名库都会提供默认的混淆配置,大多数情况可以不用做混淆,毕竟不属于项目的核心代码。   常用的混淆配置关键字和通配符见下表:      关于proguard的知识点就分析到这,想要了解更多的知识可参考本篇blog附录的资料及proguard官网。
    解决问题
    好了,现在回过头来看一下刚才编译错误,是不是觉得没那么难了呢?  之前编译错误提示是okio包和gson包下的某个类找不到,这两个包都是第三方开源的jar包,可以直接不混淆,并且忽略这两个包下的警告。
    -keep class com.google.gson.** { *; }
    -dontwarn okio.**
    -keep class okhttp3.** { *; }clean工程后重新打包编译,这次编译成功了(终于成功了,值得庆祝一下^_^)。  注意:虽然编译成功了,但一定要将应用跑起来,把各个功能都验证一下,看是否会影响。   编译后查看output文件夹,我们可以看到除了生成混淆后的apk文件后,还生成了一个mapping文件夹,如下图:    那这些文件有什么作用呢? 前言
    

    ==

网上关于使用 proguard 进行混淆的文章很多,但大部分是从讲解 proguard 知识点这个角度去写的,为什么要混淆,如何去混淆,混淆的注意点等重要的问题反而都没有写。
所以想通过这篇文章来记录我学习 proguard 的过程,一来是整理 android 混淆相关的知识点,二来是总结此次学习混淆的方法,提高学习能力。

proguard 实践

学习任何技术之前应该要有一个明确的目标,本次学习混淆的目标就是会使用 android studio 自带的 proguard 对 APP 项目进行混淆,混淆后的 APP 要能正常运行。
本次学习尝试采用“三段式理论”,三段式即提出问题,分析问题,解决问题

提出问题

  • APP 为什么要进行混淆?
    混淆的首要目的是保护 APP 项目的代码,使 APP 更难被破解;其次是优化 APP,去除无用代码和资源,减小 APP 的大小。
  • 为什么使用 proguard 进行混淆?
    因为 proguard 是一个很优秀的开源混淆代码项目,而且是 google 的 android studio 默认支持的混淆插件。
  • 怎么使用 proguard 进行混淆?
    这个就是后续要重点要分享和实践的内容了,别着急,往后看
  • 如何验证核心代码是否混淆了?
    我配置了 proguard,用 proguard 打包生成了 APK,那我怎么保证我想要混淆的代码已经混淆了呢。
    其实反过来想就行了,就是通过反编译来验证,本篇将用jadx反编译工具来做。

分析问题

经过前面的分析,我们了解到主要的问题集中在如何混淆和混淆验证两个方面上。曾经有某个伟人说过“实践是检验真理的唯一标准”,不知道怎么混淆,那就用起来试试。
接下来我们将通过去混淆 OkHttpDemo 这个项目来进行实践,源码下载请戳这里

开始混淆

  1. 将项目的build.gradle 中的minifyEnabled 置为true

    apply plugin: ‘com.android.application’

    android {
    compileSdkVersion 26
    buildToolsVersion “26.0.2”
    defaultConfig {
    applicationId “com.torch.easydev.okhttpdemo”
    minSdkVersion 17
    targetSdkVersion 26
    versionCode 1
    versionName “1.0”
    testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
    }
    buildTypes {
    release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
    }
    }
    }

    dependencies {
    compile fileTree(dir: ‘libs’, include: [’*.jar’])
    androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.2’, {
    exclude group: ‘com.android.support’, module: ‘support-annotations’
    })
    compile ‘com.android.support:appcompat-v7:26.+’
    testCompile ‘junit:junit:4.12’
    compile ‘com.squareup.okhttp3:okhttp:3.7.0’
    compile ‘com.google.code.gson:gson:2.8.0’
    }

2.使用assembleRealease 打包 APK,注意这里不能直接 run,默认编译运行的 APP 为 debug 版本,是不会进行混淆的。
这里写图片描述

3. 打包过程会发现报错了,提示 okio 包下的某个类找不到,以下是编译的详细过程。

// 此Task为proguard混淆的命令
:app:transformClassesAndResourcesWithProguardForRelease
// proguard 版本
ProGuard, version 5.3.2
Reading input...
// 读取的jar,包括libs/以及build.gradle的dependencies里用到的
Reading program jar [/Users/torch/.android/build-cache/7880ba2afce31630824a0110efd86274bc49bc30/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/e6f71d5707a837d00ed85447f14b707f76ae20f0/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.7.0/6edbebdd8868708db718d15c09c4b28037fd487e/okhttp-3.7.0.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.0/c4ba5371a29ac9b2ad6129b1d39ea38750043eff/gson-2.8.0.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/0d42c3690aeb29caef324c71b7fc2421c8de1e5b/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/b9555cd093d474b08b7ea2c011a82c2d4c627b86/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.12.0/3742beff8024d0a0073d284b7c5e4cbf73d99b25/okio-1.12.0.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/cb09c2323f3dd0b058deff4426ce2887c32c5698/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/Library/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/26.0.0-alpha1/support-annotations-26.0.0-alpha1.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/446d05455618d1e0c86af9ae0a41a910627b74e1/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/5a18253d3fd47698167249e46562f1794b833a76/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/0d75c8d0e60a38ec97c92a95e11ba8c609f3847d/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/a23ef26dcf19d0326ebc9315975b5fd8c0e068f1/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/transforms/mergeJavaRes/release/jars/2/1f/main.jar] (filtered)
Reading program directory [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/classes/release] (filtered)
Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/android.jar]
Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/optional/org.apache.http.legacy.jar]
Note: duplicate definition of library class [org.apache.http.params.HttpConnectionParams]
Note: duplicate definition of library class [org.apache.http.params.HttpParams]
Note: duplicate definition of library class [org.apache.http.params.CoreConnectionPNames]
Note: duplicate definition of library class [org.apache.http.conn.ConnectTimeoutException]
Note: duplicate definition of library class [org.apache.http.conn.scheme.HostNameResolver]
Note: duplicate definition of library class [org.apache.http.conn.scheme.SocketFactory]
Note: duplicate definition of library class [org.apache.http.conn.scheme.LayeredSocketFactory]
Note: duplicate definition of library class [android.net.http.SslCertificate$DName]
Note: duplicate definition of library class [android.net.http.SslCertificate]
Note: duplicate definition of library class [android.net.http.HttpResponseCache]
Note: duplicate definition of library class [android.net.http.SslError]

// proguard 会优化、去除重复的类
Note: there were 11 duplicate class definitions.
      (http://proguard.sourceforge.net/manual/troubleshooting.html#duplicateclass)

Initializing...

// 找不到okio包下的三个类
Warning: okio.DeflaterSink: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

// 找不到gson包和okio包下的类
Note: com.google.gson.internal.UnsafeAllocator: can't find dynamically referenced class sun.misc.Unsafe
Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class com.android.org.conscrypt.SSLParametersImpl
Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
Note: okhttp3.internal.platform.AndroidPlatform$CloseGuard: can't find dynamically referenced class dalvik.system.CloseGuard
Note: okhttp3.internal.platform.Platform: can't find dynamically referenced class sun.security.ssl.SSLContextImpl
Note: com.google.gson.internal.UnsafeAllocator accesses a declared field 'theUnsafe' dynamically
Note: there were 5 unresolved dynamic references to classes or interfaces.
      You should check if you need to specify additional program jars.
      (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass)
Note: there were 1 accesses to class members by means of introspection.
      You should consider explicitly keeping the mentioned class members
      (using '-keep' or '-keepclassmembers').
      (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclassmember)

Warning: there were 3 unresolved references to classes or interfaces.
         You may need to add missing library jars or update their versions.
         If your code works fine without the missing classes, you can suppress
         the warnings with '-dontwarn' options.
         (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)

Warning: Exception while processing task java.io.IOException: Please correct the above warnings first.
:app:transformClassesAndResourcesWithProguardForRelease FAILED

FAILURE: Build failed with an exception.

// 执行proguard混淆失败
* What went wrong:
Execution failed for task ':app:transformClassesAndResourcesWithProguardForRelease'.
> Job failed, see logs for details

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 5.691 secs

以上是 proguard 混淆的整个编译过程,发现混淆失败报错了,但明明代码是对的,用 debug 编译运行都没问题,那是不是混淆的哪个步骤出错了呢?
接下来我们一起来了解一下 android studio 具体是怎么使用 proguard 进行编译混淆的。

proguard 相关知识点

Proguard 是一个 Java 类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。
这里写图片描述

  1. 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)
  2. 优化(Optimize):对字节码进行优化,移除无用的指令
  3. 混淆(Obfuscate):使用 a,b,c,d 这样简短而无意义的名称,对类、字段和方法进行重命名
  4. 预检(Preveirfy):在 Java 平台上对处理后的代码进行预检,确保加载的 class 文件是可执行的

混淆就是移除没有用到的代码,然后对代码里面的类、变量、方法重命名为人可读性很差的简短名字。
那么有一个问题,ProGuard 怎么知道这个代码没有被用到呢?
这里引入一个Entry Point(入口点)概念,Entry Point 是在 ProGuard 过程中不会被处理的类或方法。在压缩的步骤中,ProGuard 会从上述的 Entry Point 开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非 Entry Point 的类、方法都会被设置为 private、static 或 final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard 会对非 Entry Point 的类和方法进行重命名。
那么这个入口点怎么来呢?就是从 ProGuard 的配置文件来,只要有这个配置了,那么就不会被移除。

 buildTypes {
        release {
            minifyEnabled true
            # 这里的proguard-android.txt以及proguard-rules.pro就是入口点的配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

proguard-android.txt文件在Android_SDK_Home/tools/proguard 文件夹下,是 google 官方提供的一个 android app 项目的默认通用配置文件,适用于所有 android app 项目。而proguard-rules.pro默认为空,可以在这里放置一些和特定 app 相关的配置。

## proguard-rules.pro文件
# 本文件用于添加应用特有混淆配置信息,这些信息会添加在默认配置信息中,作为入口点的判断依据
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

接下来我们来一起看一下 proguard-android.txt 里面有哪些东西,默认哪些东西不能被混淆,了解了这些,我们对如何编译自定义的 proguard-rules 就能有一个大致的理解了。

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.

# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames

# 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclasses

# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后可以使用printmapping指定映射文件的名称
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
# 不启用优化,建议使用此选项
# proguard的优化选项和java虚拟机中的字节码dex优化有冲突,可能会产生一些未知的问题
-dontoptimize

# 不做预校验,preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify

# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

# 保留注解,因为注解是通过反射机制来实现的
-keepattributes *Annotation*

# 保留google的授权服务
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# 保留本地NDK方法
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留View中的set和get方法
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# 保留Activity当中的View相关方法
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# 保留枚举类
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留序列号相关类即方法
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

# 保留资源应用名
-keepclassmembers class **.R$* {
    public static <fields>;
}


# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
# 忽略support包下的警告
-dontwarn android.support.**

# Understand the @Keep support annotation.
# 保留support包下的动画
-keep class android.support.annotation.Keep

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

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

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

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

可以看到,这里主要保留的是一些不能被混淆的类、方法或者变量名,编写配置文件的核心思想就是尽可能地去做混淆,除非有不得不混淆的理由
那哪些东西是不能做混淆的呢?
以下列举一些常用的不能被混淆的情况:

  1. 本地方法:因为本地方法是根据方法名去调用的,若混淆后会导致找不到此方法名。
  2. 反射相关的方法和类:反射原理就是通过方法名和类名去实例化相应的对象,调用相关的方法,当然也不能混淆。
  3. setXX 和 getXX 方法:这里指的是通过配置文件直接生成相应的 set 和 get 方法的相关库,所以 javabean 类很多情况下不能做混淆。
  4. 第三方 jar 包:这个需要具体情况具体分析,很多知名库都会提供默认的混淆配置,大多数情况可以不用做混淆,毕竟不属于项目的核心代码。
    常用的混淆配置关键字和通配符见下表:
    这里写图片描述
    这里写图片描述
    关于 proguard 的知识点就分析到这,想要了解更多的知识可参考本篇 blog 附录的资料及 proguard 官网。

解决问题

好了,现在回过头来看一下刚才编译错误,是不是觉得没那么难了呢?
之前编译错误提示是 okio 包和 gson 包下的某个类找不到,这两个包都是第三方开源的 jar 包,可以直接不混淆,并且忽略这两个包下的警告。

-keep class com.google.gson.** { *; }
-dontwarn okio.**
-keep class okhttp3.** { *; }

clean 工程后重新打包编译,这次编译成功了(终于成功了,值得庆祝一下_)。
**注意:**虽然编译成功了,但一定要将应用跑起来,把各个功能都验证一下,看是否会影响。

编译后查看output文件夹,我们可以看到除了生成混淆后的 apk 文件后,还生成了一个mapping文件夹,如下图:
这里写图片描述
那这些文件有什么作用呢?
打开mapping.txt看一下,可以发现这是一个混淆前和混淆后的类和类的方法、变量的映射文件。当想通过日志去排查代码问题时,就需要借助这个mapping.txt文件了,因为混淆后,很多类名、方法名都已经面目全非了。

// 这是okhttphelper类的对应映射表
com.torch.easydev.okhttpdemo.OkHttpHelper -> com.torch.easydev.okhttpdemo.a:
    okhttp3.MediaType JSON -> a
    okhttp3.MediaType MARKDOWN -> b
    com.google.gson.Gson gson -> c
    java.util.concurrent.ScheduledExecutorService executor -> d
    okhttp3.OkHttpClient httpClient -> e
    okhttp3.OkHttpClient httpsClient -> f
    okhttp3.OkHttpClient getOkHttpClient(android.content.Context,boolean) -> a
    okhttp3.OkHttpClient getOkHttpClient(android.content.Context,boolean,boolean) -> a
    okhttp3.OkHttpClient getOkHttpClientByCertificate(java.io.InputStream[]) -> a
    okhttp3.OkHttpClient getUnSafedOkHttpClient() -> a
    java.lang.String doGet(android.content.Context,java.lang.String) -> a
    java.lang.String doGet(android.content.Context,java.lang.String,boolean) -> a
    java.lang.String doPostString(android.content.Context) -> a
    void downloadFile(android.content.Context,java.lang.String,java.lang.String,com.torch.easydev.okhttpdemo.OkHttpHelper$DownloadStatusListener) -> a
    void <clinit>() -> <clinit>

seed.txt描述项目保留的没有被混淆的类、方法及变量名。
usage.txt描述项目使用到的类、方法及变量名
dump.txt 描述 apk 内所有 class 文件的内部结构

使用 jadx 验证混淆

假定本项目核心代码为 OkHttpHelper 类里的方法,我们来看一下混淆后的 APK,反编译出来以后变成什么样子了。
使用命令编译后,得到反编译的代码如图:

jadx-gui app-release.apk

这里写图片描述
从图中可知,使用 proguard 混淆代码已经成功了。

参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值