Getting Started with ProGuard
In this Android tutorial, you’ll learn how to strip down your app size by making use of ProGuard – an app shrinking and obfuscation tool. By Kolin Stürt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started with ProGuard
30 mins
- Getting Started
- Using the APK Analyzer
- Adding the BubblePicker Library
- Enabling ProGuard
- Adding “Don’t Warn” Rules
- Adding BubblePicker Code
- Debugging with ProGuard Output Files
- Adding Keep Rules
- Adding Data to the BubblePicker
- Introspection and Reflection
- Enabling Advanced Optimizations
- Understanding Obfuscation
- Adding Annotations
- Where To Go From Here
Adding Data to the BubblePicker
There’s a file included in the res folder of the project called sloths.xml. Since it’s in XML format, you’ll need a way to get that data into Kotlin objects. You’ll use another third party library included with the Retrofit package called SimpleXML.
Add this code to the list of dependencies in the build.gradle app module file. You can exclude groups and modules that you’re sure you won’t use:
implementation ('com.squareup.retrofit2:converter-simplexml:2.0.0-beta3'){
exclude group: 'xpp3', module: 'xpp3'
exclude group: 'stax', module: 'stax-api'
exclude group: 'stax', module: 'stax'
}
Sync Gradle, then build and run the app.
You’ll have some new warnings: a reference to org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement, and many variations of warnings about javax.xml.stream.**.
You won’t be using Java’s XML stream feature. For this tutorial’s purpose, it’s safe to ignore the animal_sniffer warnings. Add the following to the proguard-rules.pro file:
-dontwarn javax.xml.stream.**
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
Select Build ▸ Make Project. This time the build should succeed.
Open the SlothViewModel.kt file and replace the loadSloths()
function with the following:
private fun loadSloths(resources: Resources) { // 1
val serializer = Persister()
val inputStream = resources.openRawResource(R.raw.sloths) // 2
val sloths = serializer.read(Sloths::class.java, inputStream) // 3
sloths.list?.let { theList ->
val map = theList.associateBy( { it.name }, {it})
this.sloths = map.toSortedMap()
}
}
Here:
- You removed the explicit return type for the function.
- You opened the sloths XML file an an
InputStream
. - You retrieved a list of Sloths by invoking read().
The XML parser knows how to map the XML fields to your Kotlin objects by using annotations. In the Sloths.kt file, add this annotation above the Sloths
constructor:
@Root(name = "sloths", strict = false)
This tells the parser to look for the root node called “sloths”.
Then add annotations above the val list
property in the constructor:
@field:ElementList(entry = "sloth", inline = true)
@param:ElementList(entry = "sloth", inline = true)
@field
and @param
let the parser know to look for “sloth” items.
The same will need to be done for the Sloth class. In the Sloth.kt file, replace everything after the import statement with this:
@Root(name = "sloth", strict = false)
data class Sloth constructor(
@field:Element(name = "name")
@param:Element(name = "name")
var name: String = "",
@field:Element(name = "realName")
@param:Element(name = "realName")
var realName: String = "",
@field:Element(name = "imageResource")
@param:Element(name = "imageResource")
var imageResourceName: String = "",
@field:Element(name = "description")
@param:Element(name = "description")
var description: String = "") : Serializable
In the MainActivity.kt file, add a ViewModel
variable right above onCreate()
:
private val viewModel: SlothViewModel by lazy {
ViewModelProviders.of(this).get(SlothViewModel::class.java)
}
Add a call to get the sloth data as the first line of setupBubblePicker()
:
val map = viewModel.getSloths(resources)
Replace the line that reads val titles = listOf("1", "2", "3", "4", "5", "6")
with the following:
val titles = map.toList()
And finally, replace the title =
line with this:
title = titles[position].first
Build and run the app. Notice the NoSuchMethodException
crash.
You know it must be the code you just added. Doing similar debugging to what you did earlier, you can narrow the problem down to the Parameter
object inside SimpleXML. This time you’ll take a deeper look at the problem. SimpleXML works by loading XML entities presented at runtime, then instantiates Kotlin counterparts. Kotlin can only do this by using introspection and reflection.
Introspection and Reflection
Sloths hang out in trees for hours and hours, not seeming to do very much. It’s probably because they’re busy introspecting and reflecting on life. In Kotlin, introspection and reflection are features of the language that inspect objects and call methods dynamically at runtime.
ProGuard looks at the static version of your app, but doesn’t actually run your app, so it cannot know which methods are reachable using introspection and reflection. Remember that ProGuard takes long class names and replaces them with smaller names. If something tries to reference a name at runtime with a constant string, the name will have changed. You can often tell this is happening with the NoSuchMethodException
.
You need to tell ProGuard to keep the sections that use reflection, like the @param
code you added. You’ll use the implements
keyword to keep all classes that extend or implement Parameter
. Add the following to the end of the ProGuard rules file:
-keep class * implements org.simpleframework.xml.core.Parameter { public *; }
While you’re at it, go ahead and add a bit more functionality to the app. In the MainActivity.kt file, add this to the onBubbleSelected()
method of the BubblePickerListener
:
val showDetailsIntent = Intent(picker.context, SlothDetailActivity::class.java)
val pet = map[item.title]
showDetailsIntent.putExtra(SLOTH_KEY, pet)
startActivity(showDetailsIntent)
item.isSelected = false
Build and run the app. You should see the bubbles populated with the different species of sloths.
Tap on a bubble to reveal more information about each species.
Congratulations! You’ve set up a cool looking app using the default ProGuard settings.
You could stop here, but if you’re interested in how the app size can be further reduced, continue reading.
Enabling Advanced Optimizations
ProGuard provides an advanced optimization profile. It’s not used as the default because it can cause further build and runtime errors. You’ll enable the advanced optimizations by swapping the default proguard-android.txt file out with proguard-android-optimize.txt.
In the build.gradle app module file, replace the proguardFiles
line in the debug section with the following:
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
Sync Gradle, then build and run the app. The build time will be much longer because ProGuard is performing more analysis and optimizations inside and across methods. It will seem very slow, much like a sloth climbing a tree.
Your app should crash with the following error:
Here, the @ symbol is in reference to an annotated class – in this case, classes annotated with org.simpleframework.xml.*
. Add a rule to the end of the ProGuard rules file to keep methods annotated with org.simpleframework.xml.*
:
-keepclassmembers class * { @org.simpleframework.xml.* *; }
This time, the error mentions a constructor instead of a missing class, which is why this code uses keepclassmembers
to keep class members only.
Build and run the app. You should see your app back up and running again.
Note: This might be a good time to stop and check the APK Analyzer again to see how the size is doing.
Note: This might be a good time to stop and check the APK Analyzer again to see how the size is doing.
You’ve set up a fancy ProGuard optimized app for your Sloth Sanctuary. The Sleuth Zoo is very competitive – they’ve been taking all your ideas and making a profit off of them. How evil! You think that when you release your app, they’re going to analyze your APK and steal your “secret sauce”. Since ProGuard performs obfuscation, this is yet another place that it can help you out.