Android Code Snippet Tips regarding JavaObject and R8 optimizer

JavaObject dynamically calls Java APIs. R8 doesn't detect these calls and can therefore remove or rename the runtime called APIs.

The B4A compiler can detect some of the cases. Check this code for example:
B4X:
Dim builder As JavaObject
builder.InitializeNewInstance("com.google.android.gms.ads.AdLoader.Builder", Array(ctxt, AdUnitId))

Dim onUnifiedAdLoadedListener As Object = builder.CreateEventFromUI("com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener", _
   "UnifiedAdLoaded", Null)
builder.RunMethod("forNativeAd", Array(onUnifiedAdLoadedListener))
Dim AdLoader As JavaObject = builder.RunMethod("build", Null)
AdLoader.RunMethod("SomeMethodThatMightFailAtRuntime")

The B4A compiler will add two "keep rules", one for com.google.android.gms.ads.AdLoader.Builder and the second for com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener.

The problematic API is here:
B4X:
Dim AdLoader As JavaObject = builder.RunMethod("build", Null)
The compiler will not add a rule for AdLoader (unknown) type and it can fail in two ways:
1. ClassNotFoundException - R8 removed or renamed the class.
2. MethodNotFoundException because R8 renamed SomeMethodThatMightFailAtRuntime.

The solution is to add a rule such as:
B4X:
-keep class com.google.android.gms.ads.AdRequest{ *; }
You can find it in the API documentation or ask an AI to do work for you.


Now for code such as this one (usually wrote by myself):
B4X:
builder.CreateEventFromUI("com/google/android/gms/ads/nativead/NativeAd.OnNativeAdLoadedListener".Replace("/", ".")
In this case the B4A compiler will not detect the class and will not automatically add a rule.
 

b4x-de

Active Member
Licensed User
Longtime User
Wouldn't it make sense for B4A to automatically add the kind of keep rule that you suggest here as the manual solution?

In the example, B4A already detects:

B4X:
com.google.android.gms.ads.AdLoader.Builder

but it does not know that:

B4X:
builder.RunMethod("build", Null)

returns an AdLoader.

Your suggested manual solution is therefore:

B4X:
-keep class com.google.android.gms.ads.AdLoader { *; }

My first thought would be: could B4A simply generate this additional rule automatically whenever it detects a nested class such as:

B4X:
AdLoader.Builder

So detecting:

B4X:
com.google.android.gms.ads.AdLoader.Builder

would automatically also keep:

B4X:
com.google.android.gms.ads.AdLoader

This would cover the very common Builder -> built object pattern without requiring B4A to analyze the return type of RunMethod.

A slightly more general variant would be to generate a wildcard rule such as:

B4X:
-keep class com.google.android.gms.ads.AdLoader* { *; }

This would also protect related nested classes and would probably cover more cases with very little additional compiler logic.

An even more conservative option would be package-wide keep rules:

B4X:
-keep class com.google.android.gms.ads.** { *; }

whenever JavaObject accesses a class from that package.

This would obviously reduce the amount of shrinking and optimization R8 can perform. But I suspect that for most B4A projects compatibility is more important than achieving the maximum possible R8 optimization. Only a minority of projects will probably depend on aggressive shrinking, whereas a runtime ClassNotFoundException or MethodNotFoundException caused by JavaObject is quite difficult to diagnose.

So perhaps a conservative automatic rule could be the safer default, while users who really care about maximum R8 optimization could still use more specific rules manually.
 

Filippo

Expert
Licensed User
Longtime User
Only a minority of projects will probably depend on aggressive shrinking, whereas a runtime ClassNotFoundException or MethodNotFoundException caused by JavaObject is quite difficult to diagnose.
In my opinion, you're right, because most of the time, errors like that cause crashes for customers, and that makes your software unpopular.
 
Top