Android 使用 Proguard 时 Volley 总是失败

Android Volley always fails with Proguard

目标是部署应用 obfuscationminification 的应用程序。没有 minification 的常规构建工作正常。但是当 minifyEnabled 切换为 true 时,所有内容也会编译,但是所有 Volley 请求都会失败并返回错误回调 (onErrorResponse),无论结果是否成功。

build.gradle 中的缩小配置:

buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    debug {
        debuggable true
    }
}

proguard-rules.pro:

##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

##---------------End: proguard configuration for Gson  ----------



##---------------Begin: proguard configuration for Spongy Castle  ----------

-keep class org.spongycastle.crypto.* {*;}
-keep class org.spongycastle.crypto.digests.* {*;}
-keep class org.spongycastle.crypto.encodings.* {*;}
-keep class org.spongycastle.crypto.engines.* {*;}
-keep class org.spongycastle.crypto.macs.* {*;}
-keep class org.spongycastle.crypto.modes.* {*;}
-keep class org.spongycastle.crypto.paddings.* {*;}
-keep class org.spongycastle.crypto.params.* {*;}
-keep class org.spongycastle.crypto.prng.* {*;}
-keep class org.spongycastle.crypto.signers.* {*;}

-keep class org.spongycastle.jcajce.provider.digest.** {*;}
-keep class org.spongycastle.jcajce.provider.keystore.** {*;}
-keep class org.spongycastle.jcajce.provider.symmetric.** {*;}
-keep class org.spongycastle.jcajce.spec.* {*;}
-keep class org.spongycastle.jce.** {*;}

-dontwarn javax.naming.**

##---------------End: proguard configuration for Spongy Castle  ----------



# Configuration for Guava 18.0
#
# disagrees with instructions provided by Guava project: https://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava

-keep class com.google.common.io.Resources {
    public static <methods>;
}
-keep class com.google.common.collect.Lists {
    public static ** reverse(**);
}
-keep class com.google.common.base.Charsets {
    public static <fields>;
}

-keep class com.google.common.base.Joiner {
    public static com.google.common.base.Joiner on(java.lang.String);
    public ** join(...);
}

-keep class com.google.common.collect.MapMakerInternalMap$ReferenceEntry
-keep class com.google.common.cache.LocalCache$ReferenceEntry

# 
-dontwarn javax.annotation.**
-dontwarn javax.inject.**
-dontwarn sun.misc.Unsafe

# Guava 19.0
-dontwarn java.lang.ClassValue
-dontwarn com.google.j2objc.annotations.Weak
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement



# Security classes for keystore support
-dontwarn java.awt.**, javax.security.**, java.beans.**



# Volley
-dontwarn com.android.volley.**
-dontwarn com.android.volley.error.**
-keep class com.android.volley.** { *; }
-keep class com.android.volley.toolbox.** { *; }
-keep class com.android.volley.Response$* { *; }
-keep class com.android.volley.Request$* { *; }
-keep class com.android.volley.RequestQueue$* { *; }
-keep class com.android.volley.toolbox.HurlStack$* { *; }
-keep class com.android.volley.toolbox.ImageLoader$* { *; }
-keep interface com.android.volley.** { *; }
-keep class org.apache.commons.logging.*

所有使用的依赖项:

compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0' 

compile 'com.google.code.gson:gson:2.7'
compile 'com.android.volley:volley:1.0.0' 
compile 'com.google.guava:guava:19.0'
compile 'org.apache.directory.studio:org.apache.commons.io:2.4'

compile 'com.madgag.spongycastle:core:1.54.0.0'
compile 'com.madgag.spongycastle:prov:1.54.0.0'
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
compile 'com.madgag.spongycastle:pg:1.54.0.0'

除了 Volley 失败之外,Guava 中的 EventBus 也无法正常工作(订阅事件未获取)。我们有解决这些问题的方法吗?我应该在此处添加任何其他信息吗?

很难用日志查明任何错误,但是您应该在不使用混淆器的情况下尝试它。跳过 proguard 文件语法,看看它是否工作正常。 请确保您已启用 multidex,否则您的项目将无法执行。

android {

compileSdkVersion 21
buildToolsVersion "21.1.0"

defaultConfig {
    ...
    minSdkVersion 14
    targetSdkVersion 21
    ...

    // Enabling multidex support.
    multiDexEnabled true
}
...
}

dependencies {
    compile 'com.android.support:multidex:1.0.0'
}

更多详情:https://developer.android.com/studio/build/multidex.html

应该承认,即使提供了所有信息,我的问题也很难分析,因为描述的错误有很多可能的来源。

我将从问题的结尾开始。 Guava 无法正常工作,因为 ProGuard 只是从我要打包的代码中排除了 Guava 的订阅方法。 ProGuard 删除了未使用的代码,只要 Subscribe-methods 被分析为未使用(甚至 IDE 不要将它们突出显示为已使用的代码),ProGuard 已决定删除这些方法。为了解决这个问题,我们应该从 ProGuard 的处理中保留 Subscribe-methods:

# Keep subscribe-methods from deletion
-keepclassmembers class ** {
  @com.google.common.eventbus.Subscribe <methods>;
}

我的第一个问题 - 当 Volley 总是在所有被触发的请求中调用 onErrorResponse 回调。我为 Json-repsonses 使用了自定义反序列化器,它还检查服务器是否提供了一些必填字段(标有相应的注释)。当然,ProGuard 默认情况下无法与这些注释和反序列化器一起正常工作——这就是为什么我也必须保留这些实体的原因:

# To make right deserialization
-keepclassmembers class ** {
  @com.some.package.server.JsonDeserializerWithOptions$FieldRequired public *;
}
-keep @interface com.some.package.server.JsonDeserializerWithOptions$FieldRequired
-keep class com.some.package.server.JsonDeserializerWithOptions