Capture one log per journey from the instrumented debug build. The examples use the journey settings, the application ID com.example.app and a device with API level 29 or higher.
./gradlew :app:assembleDebug mkdir -p app/build/r8-assistant java -jar "$R8_ASSISTANT_JAR" instrument \ --input app/build/outputs/apk/debug/app-debug.apk \ --output app/build/r8-assistant/app-debug-instrumented.apk
With product flavors, the APK path contains the flavor, for example app/build/outputs/apk/free/debug/app-free-debug.apk.
instrument refuses an APK that is neither debuggable nor signed only with the debug key. It aligns the instrumented APK and signs it with the debug keystore.
adb install -r app/build/r8-assistant/app-debug-instrumented.apk
Always install with adb install -r. ./gradlew installDebug and connectedAndroidTest install the APK without instrumentation, and connectedAndroidTest also uninstalls the app, which deletes its data. If the install fails, see references/troubleshooting.md.
The app writes the log to a path that depends on the API level of the device:
/sdcard/Android/media/com.example.app/additional_test_output/reflection_log.json/sdcard/Android/data/com.example.app/files/test_data/reflection_log.jsonThe path uses the application ID of the installed APK, including any applicationIdSuffix of the debug build.
Stop the app, delete the old log, and launch the app:
log=/sdcard/Android/media/com.example.app/additional_test_output/reflection_log.json adb shell am force-stop com.example.app adb shell rm -f "$log" adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1
Run the journey as described in references/journeys.md. Then pull the log into the state directory and delete the copy on the device:
log=/sdcard/Android/media/com.example.app/additional_test_output/reflection_log.json adb pull "$log" app/build/r8-assistant/debug-settings.json adb shell rm -f "$log"
Repeat for every journey, with one file per journey, such as debug-sync.json for the journey sync.
If the instrumented debug build crashes in the same way as the release build, R8 is not the cause. Report the crash and stop.
The log has one JSON object per line.
Before each distinct reflective call, the app writes a request line with the event type, an ID, the top five frames of the call stack, and the arguments. After the call returns, it writes the result for that ID:
{"event": "CLASS_FOR_NAME", "id": 3, "stack": ["com.example.app.settings.ThemeLoader.load(ThemeLoader.java:42)", "com.example.app.settings.SettingsFragment.onViewCreated(SettingsFragment.kt:31)"], "args": ["com.example.app.settings.DarkTheme", "true", "null"]} {"returned": 3, "result": "com.example.app.settings.DarkTheme"}
The event types are CLASS_FOR_NAME, CLASS_NEW_INSTANCE, CLASS_GET_METHOD, CLASS_GET_DECLARED_METHOD, CLASS_GET_METHODS, CLASS_GET_DECLARED_METHODS, CLASS_GET_FIELD, CLASS_GET_DECLARED_FIELD, CLASS_GET_FIELDS, CLASS_GET_DECLARED_FIELDS, CLASS_GET_CONSTRUCTOR, CLASS_GET_DECLARED_CONSTRUCTOR, CLASS_GET_CONSTRUCTORS, CLASS_GET_DECLARED_CONSTRUCTORS, CLASS_GET_NAME, CLASS_GET_PACKAGE, CLASS_GET_SUPERCLASS, CLASS_GET_GENERIC_SUPERCLASS, CLASS_GET_GENERIC_INTERFACES, CLASS_GET_COMPONENT_TYPE, CLASS_IS_ASSIGNABLE_FROM, CLASS_IS_INSTANCE, CLASS_CAST, CLASS_AS_SUBCLASS, CLASS_FLAG, CLASS_LOADER_LOAD_CLASS, ATOMIC_FIELD_UPDATER_NEW_UPDATER, PROXY_NEW_PROXY_INSTANCE, SERVICE_LOADER_LOAD and RESOURCE_BUNDLE_GET_BUNDLE.
Results are class names, member signatures or booleans, and an object shows as the name of its class. Two kinds of results need a note:
RESOURCE_BUNDLE_GET_BUNDLE returns the class of the bundle, such as com.example.app.i18n.Messages_en, or java.util.PropertyResourceBundle when the bundle is a properties file.CLASS_GET_GENERIC_SUPERCLASS and CLASS_GET_GENERIC_INTERFACES return the name of each plain class, and <raw type><...> (such as com.google.gson.reflect.TypeToken<...>) for each parameterized type, or the runtime class name (libcore.reflect.ParameterizedTypeImpl on ART) when getRawType throws.java., javax. and android. classes. It also leaves out reflection that java., javax., android., androidx., kotlin. and kotlinx. code does on androidx., kotlin. and kotlinx. classes.LayoutInflater, has no events.loadClass calls through a variable of a subclass type, such as DexClassLoader, and super.loadClass calls have no events.ResourceBundle.getBundle overloads that take a java.lang.Module have no events.SERVICE_LOADER_LOAD records the service type. The providers are created later, while the app iterates over the loader, and have no events of their own.The log contains class names, member names, stack frames and the string arguments of reflective calls, but no field values.
Keep the logs in the state directory, delete each log on the device after the pull, and send the logs nowhere.