0

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.

keyboard
  • 2,137
  • 1
  • 20
  • 32
  • Is this what you need? - https://stackoverflow.com/questions/17259421/java-creating-a-subclass-dynamically –  Aug 17 '17 at 15:52
  • This looks very good, I'm currently trying to get ByteBuddy to run, but I'm a bit stuck at having my dynamic class call the constructor of it's superclass, please see my edit. – keyboard Aug 17 '17 at 23:55

1 Answers1

1

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);
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thank you! That brought me a good step further! It's great that you're active here! Unfortunately, I'm now facing a runtime exception when running the code (see my edit above). Do you have any idea what's going wrong? – keyboard Aug 18 '17 at 23:16
  • You are using Android, in this case use the AndroidClassLoadingStrategy in the byte-buddy-android project. – Rafael Winterhalter Aug 19 '17 at 07:25
  • Thanks for the hint, I've changed it (see Edit) and I'm still facing the same runtime exception. Any more ideas? ;) – keyboard Aug 19 '17 at 11:43
  • You are using the same strategy as before, you just access the default strategy via the subclass. You need to instantiate the android class loading strategy yourself. – Rafael Winterhalter Aug 19 '17 at 14:47
  • Thank you, it does work work. For the 'private directoy' in the constructor, I just used some directory in the app's file dirs - I hope that's alright. I'd also like to add a specific method with some code...but I'll fiddle a bit - maybe opening another question if I don't make it. Thanks a lot so far! – keyboard Aug 19 '17 at 17:17
  • It needs to be application private to pass Androids sandbox security checks but if it works, its good as it is. – Rafael Winterhalter Aug 19 '17 at 18:50
  • Ps: make sure to cache the generated types. – Rafael Winterhalter Aug 19 '17 at 18:53
  • Yep, it's application private thanks! How do you mean Cache? What do I need to do for that and why is it important? – keyboard Aug 19 '17 at 19:06
  • Rather reuse classes as Byte Buddy will recreate them. Have a look at the TypeCache. Class creation is expensive. – Rafael Winterhalter Aug 19 '17 at 20:23