SReverseby Simpa Labs

Build optimization

How R8 changes Android reverse engineering

R8 removes code, inlines methods and merges types before the APK is packaged. By the time it finishes, the shipped program can differ from the source in ways that break a name based search.

R8 works on the program, not on the file

R8 runs inside the Android build after compilation and before packaging. It replaces ProGuard in the standard Gradle pipeline and produces the dex the app ships. Its passes are usually described as shrinking, optimization, obfuscation and preverification, and the part that matters for analysis is that the passes interact. Shrinking removes code R8 cannot reach, optimization rewrites what remains, and obfuscation renames the result.

Because those passes interact, the shipped program is not the source with new names. It is a smaller program that has been restructured to do the same work.

What the optimizer does to structure

  • Unused classes, methods and fields are removed, including code that only supported a path the build never reaches.
  • Short methods are inlined into their callers, so a signer or a small parser can exist only as statements inside the method that used to call it.
  • Classes and interfaces are merged when the optimizer can show that the split does not matter, which removes the type boundaries an analysis would use to orient itself.
  • Constant expressions are folded, and a branch whose condition is now constant is removed along with the code on the dead side.
  • Lambdas and other synthetic constructs can appear as generated classes whose names carry no meaning.

Any of these can explain why a search for a class the source clearly had returns nothing at all.

Optimizations that break the decompiler

Some R8 output is hard to read for reasons unrelated to names. Compiler-generated accessors are removed once their callers move, bridge methods disappear, and exception handling is restructured, so a try block in the decompiled output may not match the one in the source. Constant and enum handling can also change shape, with small helper types replaced by integers or folded away.

These are artifacts of optimization rather than deliberate protection, and they are recognizable because the surrounding code still reads normally. Where a method looks like it was assembled by a different author, compare it with its caller and assume the compiler merged the two.

What R8 keeps readable

R8 has to leave intact anything the platform resolves at run time, and it does not encrypt data. Several categories survive because of that.

  • Manifest components and other classes the system instantiates by name.
  • Anything held by a keep rule, which covers classes named from XML, native methods and many serialization models.
  • Network constants: hosts, paths, header names and content types appear as string constants unless a protector encrypts them separately.
  • Serialized field names, which appear in JSON payloads regardless of what the class is called in dex.
  • Native libraries, which R8 does not rewrite and whose exported symbols are unchanged.

Keep rules behave differently under R8

R8 reads rules written for ProGuard, and it runs passes ProGuard does not. A rule that preserves a class in a ProGuard build can allow more aggressive work in an R8 build, because R8 is also free to shrink and merge around it. A keep rule recovered from an older project is evidence about intent rather than a guarantee about what the shipped dex contains, and the dex is where the answer is.

What a mapping file changes

A build can emit a mapping file that lists original names against shipped names. When it is available, it removes the guessing from renaming and lets retrace recover readable stack traces. It is not part of the distributed APK. A mapping for a different build can mislead, because inlining and class merging move code between owners.

Reading an optimized build

Start from behavior the user can see, then follow the values. Find the HTTP client the app builds and the code that configures it. The application classes around that client are the ones worth naming, and data flow tells you which is which. When a signer has been inlined, the operation appears inside a method that does several other things, so read that method for the arithmetic and hashing calls rather than for a function that looks like the source.

Where the reading stalls, a runtime trace resolves it. Instrument the HTTP layer, capture the target call, and compare the values in the request with the constants and parameters you found. That comparison localizes the logic that still needs reconstruction, and it usually narrows the remaining work to one method or one native function.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?