2
votes

I have an Android app whose debug builds work perfectly. Its release build however, which includes obfuscation with ProGuard (config is shown below), doesn't work and always crashes on start with the following log:

01-26 15:33:34.048 E/AndroidRuntime: FATAL EXCEPTION: main
                                 Process: com.alxdroiddev.cameragear, PID: 6551
                                 co: Fragment FragmentIntro{13af572c id=0x7f100177 android:switcher:2131755383:0} did not call through to super.onAttach()
                                     at android.support.v4.app.FragmentManagerImpl.a(SourceFile:1232)
                                     at android.support.v4.app.FragmentManagerImpl.v(SourceFile:2323)
                                     at android.support.v4.app.FragmentManagerImpl.a(SourceFile:2136)
                                     at android.support.v4.app.FragmentManagerImpl.b(SourceFile:2092)
                                     at android.support.v4.app.FragmentManagerImpl.b(SourceFile:1969)
                                     at bu.commitNowAllowingStateLoss(SourceFile:620)
                                     at android.support.v4.app.FragmentPagerAdapter.finishUpdate(SourceFile:143)
                                     at android.support.v4.view.ViewPager.populate(SourceFile:1268)
                                     at android.support.v4.view.ViewPager.populate(SourceFile:1116)
                                     at android.support.v4.view.ViewPager.onMeasure(SourceFile:1642)
                                     at android.view.View.measure(View.java:17430)
                                     at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:727)
                                     at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:463)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5463)
                                     at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
                                     at android.support.v7.widget.ContentFrameLayout.onMeasure(SourceFile:139)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5463)
                                     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1436)
                                     at android.widget.LinearLayout.measureVertical(LinearLayout.java:722)
                                     at android.widget.LinearLayout.onMeasure(LinearLayout.java:613)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5463)
                                     at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5463)
                                     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1436)
                                     at android.widget.LinearLayout.measureVertical(LinearLayout.java:722)
                                     at android.widget.LinearLayout.onMeasure(LinearLayout.java:613)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5463)
                                     at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
                                     at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2560)
                                     at android.view.View.measure(View.java:17430)
                                     at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2001)
                                     at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1166)
                                     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1372)
                                     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1054)
                                     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5779)
                                     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
                                     at android.view.Choreographer.doCallbacks(Choreographer.java:580)
                                     at android.view.Choreographer.doFrame(Choreographer.java:550)
                                     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
                                     at android.os.Handler.handleCallback(Handler.java:739)
                                     at android.os.Handler.dispatchMessage(Handler.java:95)
                                     at android.os.Looper.loop(Looper.java:135)
                                     at android.app.ActivityThread.main(ActivityThread.java:5221)
                                     at java.lang.reflect.Method.invoke(Native Method)
                                     at java.lang.reflect.Method.invoke(Method.java:372)
                                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

I have tried adding the required calls (overriding onAttach() in the faulty fragment), but that didn't make any difference. The fragment in question, is in the code below and it is used with the library AppIntro. This is the code for FragmentIntro.java:


    public class FragmentIntro extends Fragment implements ISlideBackgroundColorHolder {

        /*    Static stuff     */
        private static final String ARG_LAYOUT_TITLE_ID = "titleId";
        private static final String ARG_LAYOUT_MESSAGE_ID = "messageId";
        private static final String ARG_LAYOUT_DRAWABLE_ID = "drawableId";
        private static final String ARG_LAYOUT_COLOR_ID = "colorId";
        //private static final int layoutResId = R.layout.intro_fragment;

        /*    Private instance variables     */
        private int titleRedId, messageResId, drawableId, colorResId, colorCode;
        private RelativeLayout mainLayout;

        public static FragmentIntro newInstance(@StringRes  int titleId, @StringRes int messageId, @DrawableRes int drawableId, @ColorRes int colorId) {

            FragmentIntro fragSlide = new FragmentIntro();

            Bundle args = new Bundle();
            args.putInt(ARG_LAYOUT_TITLE_ID, titleId);
            args.putInt(ARG_LAYOUT_MESSAGE_ID , messageId);
            args.putInt(ARG_LAYOUT_DRAWABLE_ID , drawableId );
            args.putInt(ARG_LAYOUT_COLOR_ID, colorId );

            fragSlide.setArguments(args);

            return fragSlide;
        }

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            if (getArguments() != null) {
                this.titleRedId = getArguments().getInt(ARG_LAYOUT_TITLE_ID);
                this.messageResId = getArguments().getInt(ARG_LAYOUT_MESSAGE_ID);
                this.drawableId = getArguments().getInt(ARG_LAYOUT_DRAWABLE_ID);
                this.colorResId = getArguments().getInt(ARG_LAYOUT_COLOR_ID);
            }
        }

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            //super.onCreateView(inflater, container, savedInstanceState);
            //if (container == null) return null;

            View view = inflater.inflate(R.layout.intro_fragment, container, false);

            Utils.globalAppContext = getContext().getApplicationContext();

            colorCode = ContextCompat.getColor(getContext().getApplicationContext(), colorResId);

            mainLayout = (RelativeLayout) view.findViewById(R.id.introLayout);
            mainLayout.setBackgroundColor(colorCode);
            ((TextView) view.findViewById(R.id.textViewIntroTitle)).setText(titleRedId);
            ((TextView) view.findViewById(R.id.textViewIntroMessage)).setText(messageResId);
            ((ImageView) view.findViewById(R.id.imageViewIntro)).setImageDrawable(Utils.getDrawableFromResource(drawableId));

            return view;
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            outState.putInt(ARG_LAYOUT_TITLE_ID, titleRedId);
            outState.putInt(ARG_LAYOUT_MESSAGE_ID, messageResId);
            outState.putInt(ARG_LAYOUT_DRAWABLE_ID, drawableId);
            outState.putInt(ARG_LAYOUT_COLOR_ID, colorResId);
            super.onSaveInstanceState(outState);
        }

        @Keep
        @TargetApi(23)
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
        }

        @Keep
        @SuppressWarnings("deprecation")
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
        }

        @Override
        public void onPause() {
            super.onPause();
        }

        @Override
        public void onResume() {
            super.onResume();
        }

        @Override
        public void onStart() {
            super.onStart();
        }

        @Override
        public void onStop() {
            super.onStop();
        }


        // Implementation of ISlideBackgroundColorHolder

        @Override
        public int getDefaultBackgroundColor() {
            return colorCode;
        }

        @Override
        public void setBackgroundColor(@ColorInt int backgroundColor) {
            mainLayout.setBackgroundColor(backgroundColor);
        }
    }

The proguard config file I am using is this (and only this) :


    ####################################################################  GENERAL OPTIONS

    -optimizationpasses 5
    -dontusemixedcaseclassnames
    -dontskipnonpubliclibraryclasses
    -dontskipnonpubliclibraryclassmembers
    -verbose
    -dontpreverify
    -repackageclasses ''
    -allowaccessmodification
    -mergeinterfacesaggressively
    -overloadaggressively
    -keepattributes *Annotation*,Signature,EnclosingMethod,InnerClasses


    -assumenosideeffects class com.alxdroiddev.cameragear.FragmentQueryRunner { *; }
    -assumenosideeffects class com.alxdroiddev.cameragear.FragmentCards { *; }
    -assumenosideeffects class com.alxdroiddev.cameragear.db.InitialSampleData { *; }

    # Required for Firebase Database (Models and POJOs)
    -keepclassmembers class com.alxdroiddev.cameragear.models.** {
      *;
    }


    ####################################################################  REMOVE LOGGING

    -assumenosideeffects class android.util.Log {
        public static *** e(...);
        public static *** w(...);
        public static *** wtf(...);
        public static *** d(...);
        public static *** v(...);
        public static *** i(...);
    }

    -assumenosideeffects class com.alxdroiddev.utils.CLog { *; }


    ####################################################################  IMAGECROPPER

    -keepnames class com.theartofdev.edmodo.cropper.** { *; }

    ####################################################################  APPINTRO

    -keep class com.github.paolorotolo.** {*;}
    -keep public class com.alxdroiddev.cameragear.utils.FragmentIntro
    -keepclassmembers public class com.alxdroiddev.cameragear.utils.FragmentIntro {*;}
    -keep public class com.alxdroiddev.cameragear.ActivityIntro
    -keepclassmembers public class com.alxdroiddev.cameragear.ActivityIntro {*;}

    ####################################################################  METADATA-EXTRACTOR

    -keep class com.drew.imaging.** { *; }
    -keep class com.drew.metadata.** { *; }
    -keep class com.drew.lang.** { *; }

    ####################################################################  MISC STUFF


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

    # Needed for Parcelable/SafeParcelable Creators to not get stripped
    -keepnames class * implements android.os.Parcelable {
        public static final ** CREATOR;
    }

    -keepclassmembers class * implements android.os.Parcelable {
        static ** CREATOR;
    }

    # Needed when building against pre-Marshmallow SDK.
    -dontwarn android.security.NetworkSecurityPolicy


    -keep class android.support.customtabs.** { *; }
    -dontwarn android.support.customtabs.** 

    -keep class com.google.android.gms.** { *; }
    -dontwarn com.google.android.gms.**

    #################################################################### IN-APP BILLING

    -keep public interface com.android.vending.licensing.ILicensingService
    -keep public class com.google.vending.licensing.ILicensingService
    -keep public class com.android.vending.licensing.ILicensingService

    ####################################################################  KEEP ANDROID SUPPORT V7 AND DESIGN

    -dontwarn android.support.design.**
    -keep class android.support.design.** { *; }
    -keep interface android.support.design.** { *; }
    -keep public class android.support.design.R$** { *; }

    -keep public class android.support.v7.widget.** { *; }
    -keep public class android.support.v7.internal.widget.** { *; }
    -keep public class android.support.v7.internal.view.menu.** { *; }

    -keep public class * extends android.support.v4.view.ActionProvider {
        public (android.content.Context);
    }

    -keep public class android.support.v14.preference.** { *; }
    -keep public class android.support.v7.app.** { *; }
    -keep public class android.support.v7.preference.** { *; }
    -keep public class android.support.v4.app.** { *; }



    -dontwarn android.support.**
    -keep interface android.support.v4.** { *; }
    -keep interface android.support.v7.** { *; }
    -keep interface android.support.v13.** {*; }
    -keep interface android.support.v14.** {*; }

    ####################################################################  ORG.APACHE.HTTP

    -keep class org.apache.http.** { *; }
    -keep interface org.apache.http.**
    -dontwarn org.apache.**

    ####################################################################  GOOGLE PLAY SERVICES LIB - ADS

    -keep public class com.google.android.gms.** { public *; }
    #-keep class com.google.android.gms.**

    # For Google Play Services
    -keep public class com.google.android.gms.ads.**{
       public *;
    }

    ####################################################################  FIREBASE

    -keep public class com.google.firebase.** { *; }
    -keep public class com.google.firebase.analytics.** { *; }
    -keep public class com.google.firebase.provider.** { *; }
    -keep public class com.google.firebase.auth.** { *; }
    -keep interface com.google.firebase.** {*; }
    -keep class com.firebase.** { *; }


    #################################################################### SUGGESTED OPTIMIZATIONS BY GOOGLE


    # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
    -keepclasseswithmembernames class * {
        native ;
    }

    # 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*();
    }

    # 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 **.R$* {
        public static ;
    }

    # Understand the @Keep support annotation.
    -keep class android.support.annotation.Keep

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

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

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

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

I've lost track of how many hours I've spent trying to solve this, specially because everything runs in debug mode, and with no complaint about the super.onAttach(). It only fails after obfuscating with the config above.

Thank you very much for your attention and any help is appreciated.

Best regards.

3

3 Answers

3
votes

I got it working. Like Doron suggested, it probably was related to the obfuscation, and not to the code itself.

I'll post my methodology here and the final proguard.cfg file, just to serve as a reference for people facing the same problem and how to [potentially] solve it (beware: it involves lots of elbow grease!).

Since I also suspected it had nothing to do with the classes or the code, the first thing I did was to remove all the unnecessary overrides, let both classes be obfuscated and disable all optimizations. This was done with these settings in my ProGuard config:

-optimizations no_optimizations

Explanation: no_optimizations is just an empty placeholder. It means nothing to ProGuard. You could very well use the word "cucumber" in there and it would have the same effect. It just means that no optmizations will be run. It is different from not specifying the -optimizations instruction, which means that all the optimizations will be run.

I also commented out all the -keep for the FragmentIntro class and ActivityIntro. I did a release compile and tested. It ran gracefully! All the other obfuscation options shown in the file above (in the OP) were turned on and left as they were.

Then the elbow grease began: I created a line were each and every ProGuard optimizations were denied explicitely:

-optimizations !class/marking/final,!class/unboxing/enum,!class/merging/vertical,!class/merging/horizontal,!field/removal/writeonly,!field/marking/private,!field/propagation/value,!method/marking/private,!method/marking/static,!method/marking/final,!method/removal/parameter,!method/propagation/parameter,!method/propagation/returnvalue,!method/inlining/short,!method/inlining/unique,!method/inlining/tailrecursion,!code/merging,!code/simplification/variable,!code/simplification/arithmetic,!code/simplification/cast,!code/simplification/field,!code/simplification/branch,!code/simplification/string,!code/simplification/advanced,!code/removal/advanced,!code/removal/simple,!code/removal/variable,!code/removal/exception,!code/allocation/variable

I then started allowing 1 optimization at a time and each time I did a release build, installed on the emulator and saw if the app ran.

If that optimization resulted in a running app, I left it without the exclamation mark ( ! ), meaning it would be allowed, otherwise I'd put the exclamation mark back and move on to the next optimization: remove the mark, release build, install, test.

I did this 29 times, one for each optimization, just to figure out what optimizations were making my code crash.

At the end, I got all the unwanted optimizations figured out and got this:

-optimizations class/marking/final,class/unboxing/enum,class/merging/vertical,class/merging/horizontal,!field/removal/writeonly,field/marking/private,field/propagation/value,method/marking/private,!method/marking/static,method/marking/final,!method/removal/parameter,method/propagation/parameter,method/propagation/returnvalue,method/inlining/short,method/inlining/unique,method/inlining/tailrecursion,code/merging,code/simplification/variable,code/simplification/arithmetic,code/simplification/cast,code/simplification/field,code/simplification/branch,code/simplification/string,code/simplification/advanced,!code/removal/advanced,code/removal/simple,code/removal/variable,code/removal/exception,!code/allocation/variable

The line above, cleaned up and only denying (the exclamation mark) the unwanted optmizations, is equivalent to:

-optimizations !field/removal/writeonly,!method/marking/static,!method/removal/parameter,!code/removal/advanced,!code/allocation/variable

With the optimizations above not done, my code compiled and ran fine. On the other hand, if I allow any of the optimizations above, it will crash on start.

This is how my cleaned up, working proguard.cfg file ended up:

####################################################################  GENERAL OPTIONS

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-verbose
-dontpreverify
-repackageclasses ''
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
-keepattributes *Annotation*,Signature,EnclosingMethod,InnerClasses


-optimizations !field/removal/writeonly,!method/marking/static,!method/removal/parameter,!code/removal/advanced,!code/allocation/variable


-assumenosideeffects class com.alxdroiddev.cameragear.FragmentQueryRunner { *; }
-assumenosideeffects class com.alxdroiddev.cameragear.FragmentCards { *; }
-assumenosideeffects class com.alxdroiddev.cameragear.db.InitialSampleData { *; }

# Required for Firebase Database (Models and POJOs)
-keepclassmembers class com.alxdroiddev.cameragear.models.** {
  *;
}


####################################################################  REMOVE LOGGING

-assumenosideeffects class android.util.Log {
    public static *** e(...);
    public static *** w(...);
    public static *** wtf(...);
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
}

-assumenosideeffects class com.alxdroiddev.utils.CLog { *; }


####################################################################  IMAGECROPPER

-keepnames class com.theartofdev.edmodo.cropper.** { *; }

####################################################################  APPINTRO

-keep class com.github.paolorotolo.** {*;}

####################################################################  METADATA-EXTRACTOR

-keep class com.drew.imaging.** { *; }
-keep class com.drew.metadata.** { *; }
-keep class com.drew.lang.** { *; }

####################################################################  MISC STUFF


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

# Needed for Parcelable/SafeParcelable Creators to not get stripped
-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

-keepclassmembers class * implements android.os.Parcelable {
    static ** CREATOR;
}

# Needed when building against pre-Marshmallow SDK.
-dontwarn android.security.NetworkSecurityPolicy


-keep class android.support.customtabs.** { *; }
-dontwarn android.support.customtabs.** 

-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**

#################################################################### IN-APP BILLING

-keep public interface com.android.vending.licensing.ILicensingService
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

####################################################################  KEEP ANDROID SUPPORT V7 AND DESIGN

-dontwarn android.support.design.**
-keep class android.support.design.** { *; }
-keep interface android.support.design.** { *; }
-keep public class android.support.design.R$** { *; }

-keep public class android.support.v7.widget.** { *; }
-keep public class android.support.v7.internal.widget.** { *; }
-keep public class android.support.v7.internal.view.menu.** { *; }

-keep public class * extends android.support.v4.view.ActionProvider {
    public <init>(android.content.Context);
}

-keep public class android.support.v14.preference.** { *; }
-keep public class android.support.v7.app.** { *; }
-keep public class android.support.v7.preference.** { *; }
-keep public class android.support.v4.app.** { *; }



-dontwarn android.support.**
-keep interface android.support.v4.** { *; }
-keep interface android.support.v7.** { *; }
-keep interface android.support.v13.** {*; }
-keep interface android.support.v14.** {*; }

####################################################################  ORG.APACHE.HTTP

-keep class org.apache.http.** { *; }
-keep interface org.apache.http.**
-dontwarn org.apache.**

####################################################################  GOOGLE PLAY SERVICES LIB - ADS

-keep public class com.google.android.gms.** { public *; }

# For Google Play Services
-keep public class com.google.android.gms.ads.**{
   public *;
}

####################################################################  FIREBASE

-keep public class com.google.firebase.** { *; }
-keep public class com.google.firebase.analytics.** { *; }
-keep public class com.google.firebase.provider.** { *; }
-keep public class com.google.firebase.auth.** { *; }
-keep interface com.google.firebase.** {*; }
-keep class com.firebase.** { *; }


#################################################################### SUGGESTED OPTIMIZATIONS BY GOOGLE


# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# 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*();
}

# 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 **.R$* {
    public static <fields>;
}

# Understand the @Keep support annotation.
-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>(...);
}

I don't have a definitive idea why the marked optimizations mentioned broke my code, although I suspect it optimized something in v4.app.Fragment related classes (specially v4.app.FragmentManager, in line 1230, that prevented it from properly call onAttach() and set f.mCalled to true). BTW, I am using Android support library 25.1.0.

Anyhow, thank you for your time and attention and I hope that the approach above to solving this annoying issue is of some help to someone.

1
votes

As your works in debug, the problem is probably only related to obfuscation and not your actual code.

Did you try keeping the entire class in proguard:

-keep class com.alxdroiddev.cameragear.utils.FragmentIntro { *; }
1
votes

Have you tried just removing these lines:

@Keep
@TargetApi(23)
@Override
public void onAttach(Context context) {
    super.onAttach(context);
}

@Keep
@SuppressWarnings("deprecation")
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
}