启用 Proguard 的 Joda-Time 库无法在 Android 5 上运行

Joda-Time library is not working on Android 5 with Proguard enabled

这个问题只发生在 Android 5. 应用程序崩溃写在这一点上,我创建了一个新的 DateTime 对象,如:

mStartTime = new DateTime(DateTimeZone.getDefault());

崩溃报告是:

java.lang.AbstractMethodError: abstract method "long boq.f()"
   at org.joda.time.chrono.BasicYearDateTimeField.(BasicYearDateTimeField.java:46)
   at org.joda.time.chrono.BasicChronology.assemble(BasicChronology.java:273)
   at org.joda.time.chrono.GregorianChronology.assemble(GregorianChronology.java:197)
   at org.joda.time.chrono.AssembledChronology.setFields(AssembledChronology.java:323)
   at org.joda.time.chrono.AssembledChronology.(AssembledChronology.java:102)
   at org.joda.time.chrono.BasicChronology.(BasicChronology.java:131)
   at org.joda.time.chrono.BasicGJChronology.(BasicGJChronology.java:75)
   at org.joda.time.chrono.GregorianChronology.(GregorianChronology.java:153)
   at org.joda.time.chrono.GregorianChronology.getInstance(GregorianChronology.java:133)
   at org.joda.time.chrono.GregorianChronology.getInstance(GregorianChronology.java:99)
   at org.joda.time.chrono.GregorianChronology.(GregorianChronology.java:70)
   at org.joda.time.chrono.GregorianChronology.getInstanceUTC(GregorianChronology.java:80)
   at org.joda.time.chrono.ISOChronology.(ISOChronology.java:59)
   at org.joda.time.base.BaseDateTime.(BaseDateTime.java:73)
   at org.joda.time.DateTime.(DateTime.java:184)
   at com.znapo.photo_sharer.asynctasks.GetAllSessionTask.onPostExecute(GetAllSessionTask.java:149)
   at com.znapo.photo_sharer.asynctasks.GetAllSessionTask.onPostExecute(GetAllSessionTask.java:1)
   at android.os.AsyncTask.finish(AsyncTask.java:636)
   at android.os.AsyncTask.access0(AsyncTask.java:177)
   at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:653)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5254)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

我的完整 Proguard 配置文件是:

-dontwarn org.apache.**
-dontwarn com.google.android.**
-dontskipnonpubliclibraryclassmembers

##---------------Begin: proguard configuration common for all Android apps ----------
-optimizationpasses 5
-dontpreverify
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-allowaccessmodification
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-repackageclasses ''

-keep public class * extends android.app.Activity
#-keep public class * extends android.support.v7.app.ActionBarActivity
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService

# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
-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();
}

# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

# Preserve static fields of inner classes of R classes that might be accessed
# through introspection.
-keepclassmembers class **.R$* {
  public static <fields>;
}

# Preserve the special static methods that are required in all enumeration classes.
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}


-keep public class * {
    public protected *;
}

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
##---------------End: proguard configuration common for all Android apps ----------

#for support library
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
-keep class android.support.v4.** { *; }
-keep class android.support.v13.** { *; }
-keep interface android.support.v4.** { *; }
-keep interface android.support.v13.** { *; }

#for retracing obfuscated stack traces
-keepattributes SourceFile,LineNumberTable

##---------------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.** { *; }
-keep class com.tweetstudio.tweet_studio_app.dto.** { *; }

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


##--------For Google Play services-------------##

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

##---------- End: proguard configuration for google play ---------------

##---------- proguard configuration for joda-time ---------------

-dontwarn org.joda.convert.**
-dontwarn javax.xml.bind.DatatypeConverter

以下 jar 已添加到 Joda-Time 的构建路径中:

joda-time-2.7.jar
joda-convert-1.7.jar
joda-time-2.7-javadoc.jar

joda-convert-2.7.jar 转换为 zip 并浏览 BasicYearDateTimeField 的第 46 行后,我看到了这行代码:

super(DateTimeFieldType.year(), chronology.getAverageMillisPerYear());

也许浏览一些文档会有所启发:

AbstractMethodError

Thrown when an application tries to call an abstract method. Normally, this error is caught by the compiler; this error can only occur at run time if the definition of some class has incompatibly changed since the currently executing method was last compiled.

曾承诺“long boq.f()”会被执行,但它在撒谎。如果没有重新编译应该重新编译的内容,那应该只有在 运行 时间才有可能。

我会编写一个使用 joda-time 的简单独立程序,然后 new DateTime(DateTimeZone.getDefault()); 看看问题是否容易重现。如果是,则您已将问题隔离到该版本的 joda-time。看看回滚到旧版本是否可以解决问题。

你也可以重新编译 joda-time 和任何与 joda-time 对话的东西。这样做至少应该将这个 运行 时间错误变成编译时间错误。这样应该可以很容易地看出问题所在。

你可以使用这个 github Joda-Time 库 Git Hub Joda Time

您可以添加行:-

-keep class org.joda.** { *; }

添加到您的混淆器配置中。这将防止 joda 库中的代码被删除。添加此行仅对我增加了 2.8 KB 的 apk 大小。