Run the commands on the logs of all journeys, and act on the output with the evidence levels and conventions in this file.
java -jar "$R8_ASSISTANT_JAR" suggest \ --log app/build/r8-assistant/debug-settings.json \ --output app/build/r8-assistant/suggested.pro
Pass --log once per journey to combine logs, and pass the rule files of the project with --rules to leave out what they already keep:
java -jar "$R8_ASSISTANT_JAR" suggest \ --log app/build/r8-assistant/debug-settings.json \ --log app/build/r8-assistant/debug-sync.json \ --rules app/proguard-rules.pro \ --output app/build/r8-assistant/suggested.pro
For a crash, copy only the rules that cover the classes and members in the retraced stack trace. Do not paste the whole output into a rule file.
java -jar "$R8_ASSISTANT_JAR" query \ --log app/build/r8-assistant/debug-settings.json \ --class com.example.app.settings.DarkTheme \ --stacks
Filter with --class, --member and --event, and add --stacks to print the call stack of each event. The stack ties an event to the code that made the call, which is the evidence to quote in the report.
java -jar "$R8_ASSISTANT_JAR" query \ --log app/build/r8-assistant/debug-sync.json \ --event CLASS_GET_DECLARED_FIELDS
java -jar "$R8_ASSISTANT_JAR" diff \ --log app/build/r8-assistant/debug-settings.json \ --log app/build/r8-assistant/debug-sync.json \ --rules app/proguard-rules.pro \ --output app/build/r8-assistant/proposed.pro
Pass the log of every journey and every rule file that the project owns. diff reports, for each rule, the logged events that the rule covers and the rules that no event uses.
Classify every rule that you change:
“Not observed” is weak evidence. A deletion stays only when the release replay passes for the journeys that ran the code of the rule.
-keep class without members does not keep the default constructor, so list <init> explicitly.allowobfuscation only when no code looks up the name as a string. Gson and Moshi read field names, and Class.forName, ClassLoader.loadClass and ResourceBundle.getBundle read class names.volatile modifier in rules for fields of atomic field updaters. Without it, the release build fails with IllegalArgumentException: Must be volatile type.META-INF/services for a SERVICE_LOADER_LOAD event, with their no-argument constructors.RESOURCE_BUNDLE_GET_BUNDLE event, keep the bundle classes of all locales in one rule, -keep class <base>, <base>_* { <init>(); }, where <base> is the base name in the first argument. The log shows only the device locale.RESOURCE_BUNDLE_GET_BUNDLE result of java.util.PropertyResourceBundle means that the bundle is a properties file, which needs no class rule.CLASS_GET_GENERIC_SUPERCLASS or CLASS_GET_GENERIC_INTERFACES event with a parameterized type in the debug result, add -keepattributes Signature and -keep,allowobfuscation,allowshrinking rules for the class and its generic supertype. R8 keeps attributes only for classes that a rule matches.SyncRepository$1, by its supertype with class * extends or class * implements. The number in its name changes when the code changes.Put each rule in the rule file that owns the code: the proguard-rules.pro of the app module for app code, and the consumer rules file of a library module for code in that module.
To find the owning file of an existing rule, search configuration.txt, as described in references/setup.md.
Write a provenance comment on the line before each rule, with the call site and the journey, or with the retraced exception:
# r8-assistant: Class.forName in ThemeLoader.load (journey settings). -keep class com.example.app.settings.DarkTheme { <init>(); } # r8-assistant: Gson reads the fields of SyncResponse (journey sync). -keepclassmembers class com.example.app.sync.SyncResponse { <fields>; <init>(); }
When the log has no event for a crash, write the rule from the retraced stack trace. Retrace first, so that every class name is an original name:
ClassNotFoundException or NoClassDefFoundError: R8 removed or renamed the class. Keep the class name.NoSuchMethodException: keep the method with its parameter types.NoSuchFieldException: keep the field.InstantiationException, including Fragment$InstantiationException: keep the constructor, usually <init>().IllegalArgumentException: Must be volatile type: keep the field with its volatile modifier.SerializationException from kotlinx.serialization: keep the Companion field and the serializer method of the class.MissingResourceException for a bundle base name: keep the bundle classes, as for a RESOURCE_BUNDLE_GET_BUNDLE event in the rule conventions.ClassCastException from Class to ParameterizedType, or a library error about a missing type argument: keep the generic signature, as for the generic events in the rule conventions.ExceptionInInitializerError: retrace the cause and apply the matching case in this list.# r8-assistant: Fragment$InstantiationException for SettingsFragment # (retraced logcat, journey settings). -keep class com.example.app.settings.SettingsFragment { public <init>(); }
For the official guidance on keep rules, see Add keep rules.