Search code examples
javaandroidcrashlyticsbyte-buddycrashlytics-android

Java - Create anonymous Exception subclass with a certain name


We're using Fabric / Crashlytics for sending non-crash issues to their servers and later checking out the logs and stacktraces.

Unfortunately, the Crashlytics API requires to use a different Exception subclass instance for different issue types. (In the iOS SDK, you can simply use a string for different issue types, but in the Android version, you can't).

public static void craslyticsRecordError(String issueType, String errorMsg)
{
    Exception e = new Exception(issueType + "-" + errorMsg);
    Crashlytics.logException(e);
}

Now we have quite a few different issue types, that we also dynamically define at runtime (build together from string parts). So the questions is, is it possible with some Java Vodoo / Hacks (reflection?) to create a subclass at runtime with a certain name (the name of the string that we use as the issue type) and create and instance from it.

Is this somehow possible?


I'm now fiddling with ByteBuddy, trying to get it to work - but I'm stuck at getting my dynamic class call the superclass constructor correctly. Also I'm not quite sure if I'm allowed to cast to Exception later on.


Thanks to Rafael Winterhalter I'm one step further into getting this thing to work ;) I've tried exactly the you provided:

public static void craslyticsRecordError(String issueType, String errorMsg)
{
    Class<? extends Exception> dynamicType = new ByteBuddy()
            .subclass(Exception.class)
            .name(issueType)
            .make()
            .load(Fabric.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
            .getLoaded();
    try
    {
        Exception e = dynamicType.getConstructor(String.class).newInstance(errorMsg);

        GameLog.d("[Fabric] Inner Exception: " + e);
        Crashlytics.logException(e);
    }
    catch (Exception e1)
    {
        Exception e = new Exception(issueType + "-" + errorMsg);
        GameLog.d("[Fabric] ERROR ONE");
        Crashlytics.logException(e);
        e1.printStackTrace();
    }
}

and now I'm facing this runtime exception:

java.lang.NoClassDefFoundError: net.bytebuddy.dynamic.loading.NoOpClassFileTransformer
     at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.<init>(ByteArrayClassLoader.java:136)
     at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.of(ByteArrayClassLoader.java:187)
     at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.load(ByteArrayClassLoader.java:212)
     at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$WrappingDispatcher.load(ClassLoadingStrategy.java:285)
     at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:120)
     at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79)
     at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4376)
     at org.utils.Fabric.craslyticsRecordError(Fabric.java:47)
     at android.os.MessageQueue.nativePollOnce(Native Method)
     at android.os.MessageQueue.next(MessageQueue.java:323)
     at android.os.Looper.loop(Looper.java:143)
     at android.app.ActivityThread.main(ActivityThread.java:7224)
     at java.lang.reflect.Method.invoke(Native Method)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

After now using this Android-specific code:

Class<? extends Exception> dynamicType = new ByteBuddy()
        .subclass(Exception.class)
        .name(issueType)
        .make()
        .load(Fabric.class.getClassLoader(), AndroidClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();

and using the Android-specific gradle compile line:

compile 'net.bytebuddy:byte-buddy-android:1.7.3'

I'm still facing the same runtime Exception as before.


Solution

  • By default, Byte Buddy copies the constructors of its super class. In your case, it copies all Exception constructors and you can generate an exception like this:

    Class<? extends Exception> dynamicType = new ByteBuddy()
      .subclass(Exception.class)
      .name(issueType)
      .make()
      .load(Fabric.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
      .getLoaded();
    
     Exception e = dynamicType.getConstructor(String.class).newInstance(errorMsg);