Gretel is a Gradle plugin that instruments Android artifacts by adding system trace events during compilation.
Gretel enables the tracing of third-party and dynamically generated code, such as Dagger or Hilt. Adding traces at compile time also keeps the logic of the app focused. Centralized management of system traces also allows toggling the tracing on and off for the entire application. Gretel offers a flexible way to target specific sections of an Android artifact for tracing. For example, it is possible to add traces to all lifecycle callback methods, like onCreate and onResume, of all Activities with a single instruction.
- Apply the gretel gradle plugin, see section Setup
- Specify the methods you want to be traced, see section Configuration
- Capture a system trace on the device, see here
- Perform the action in the app that you want to trace, for example launch the app
- Stop the system trace capture
- Pull the traces from the device via
adb pull /data/local/traces/ .
- Inspect the trace in Android studio (drag the trace file onto the bar of open files or open the Profiler tab and
select
+
>Load from file...
)
plugins {
id("de.awenger.gretel") version "0.5.0"
}
Gretel doesn't add any traces out of the box.
It provides a DSL that configures to which classes and methods gretel will add system traces at compile time.
For example the following snipped will make gretel add a trace to the onCreate
method of the your.app.MainActivity
class
and the onCreateView
method of the your.app.MainFragment
class.
The trace will be named MainActivity::onCreate
and your.app.MainFragment::onCreateView
android {
gretel {
traces = listOf(
defineTrace {
classes = classes(type("your.app", "MainActivity"))
methods = listOf(method("onCreate"))
},
defineTrace {
classes = classes(type("your.app", "MainFragment"))
methods = listOf(method("onCreateView"))
}
)
}
}
It is possible to omit any of these parameters to target multiple classes or methods.
For example the following snipped will add a trace to all methods of all classes in the package com.example.app.activites
.
defineTrace {
classes = classes(superType = type("com.example.app.activites"))
}
Classes can also be targeted for tracing by the interfaces they implement or the class they extend.
For example the following snipped will add traces to the onCreate
and onResume
methods of all classes that extend android.app.Activity
.
defineTrace {
classes = classes(superType = type("android.app", "Activity"))
methods = listOf(
method("onCreate"),
method("onResume")
)
}
Classes can also be targeted via annotations on the class.
However, only annotation with RetentionPolicy(CLASS)
and RetentionPolicy(RUNTIME)
are supported.
Annotations with @Retention(SOURCE)
are not supported because they are already removed at compile time when gretel adds traces.
The following snipped adds traces to all methods of classes that are annotated with @dagger.hilt.android.lifecycle.HiltViewModel
:
defineTrace {
classes = classes(annotationType = type("dagger.hilt.android.lifecycle", "HiltViewModel"))
}
- Adding traces at compile time, configured in a separate file, keeps the app code focused on the functionality.
- Gretel can add traces to third-party code or code that is generated at compile time. For example gretel can add traces to the code that is generated by Hilt or Dagger.
- Powerful targeting that enables adding traces to multiple classes and methods with a single definition. For example gretel can add traces to all lifecycle methods of all Activities in an app with a single definition.
- The centralized definition of the traces makes it trivial to turn on/off the tracing for the whole app.
The following sections showcase common use cases that add traces to specific functionality with gretel
The following snipped will add traces to all methods in classes that extend (directly or indirectly)
android.app.Application
, android.content.BroadcastReceiver
, android.app.Activity
or androidx.fragment.app.Fragment
.
This can be used to trace the lifecycle callbacks in these classes.
gretel {
traces = listOf(
defineTrace {
classes = classes(superType = type("android.app", "Application"))
},
defineTrace {
classes = classes(superType = type("android.content", "BroadcastReceiver"))
},
defineTrace {
classes = classes(superType = type("android.app", "Activity"))
},
defineTrace {
classes = classes(superType = type("androidx.fragment.app", "Fragment"))
}
)
}
Dagger/Hilt generates various classes to facilitate the dependency injection. It is impossible to modify such third party or generated code, so traces can not be added in the traditional way. Gretel can add traces to these classes and methods at compile time
gretel {
traces = listOf(
defineTrace {
classes = classes(superType = type("dagger.internal", "Factory"))
methods = listOf(method("get"))
},
defineTrace {
classes = classes(superType = type("dagger", "MembersInjector"))
},
defineTrace {
classes = classes(superType = type("dagger.android", "AndroidInjector"))
}
)
}
The gretel plugin adds the traces at app build time. The traces will be recorded via androidx.core.os.TraceCompat, by adding calls to TraceCompat::beginSection and TraceCompat::endSection to methods that are configured, see #Configuration