Android App
Features
Germain monitors Uptime, Performance and Usage of any native Android Application (SDK 32 and above). The main features of the Germain UX Mobile Library are:
Automatic crash detection for unhandled exceptions
Handled exception notification
Error notification
System event monitoring
Application event monitoring
Session monitoring
Transaction monitoring
View navigation monitoring
Fragment monitoring
Requirements
Permissions
Android App Monitoring requires the following permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
Minimum SDK
Android SDK 32
Configure
Deploy
Wizards

Installation
Gradle Installation
Add the following repository to your application build.gradle
file:
allprojects {
repositories {
// ...
maven {
url "http://gradle.germainsoftware.com/artifactory/libs-release-local"
credentials {
username 'consumer'
password 'AP6BWGtbP9TZtx2GeGvU8pE2gkX$'
}
}
}
}
Add the following dependency to your project build.gradle
file (contact Germain Support to get the latest release version)
:
dependencies {
// ...
implementation(group: 'com.germainsoftware.apm', name: 'mobile-library', version: '2022.3')
}
Maven Installation
Add the following repository to your application top build.gradle
file:
allprojects {
repositories {
// ...
maven {
url "http://gradle.germainsoftware.com/artifactory/libs-release-local"
credentials {
username 'consumer'
password 'AP6BWGtbP9TZtx2GeGvU8pE2gkX$'
}
}
}
}
Add the following dependency to your project's pom.xml
file(contact Germain Support to get the latest release version)
:
<dependency>
<groupId>com.germainsoftware.apm</groupId>
<artifactId>mobile-library</artifactId>
<version>2022.3</version>
</dependency>
Manual Installation
Few simple steps to manually install Germain UX Android App Monitoring:
Download our library in aar format (please contact Germain Support to get the latest release)
You should receive 3 components:
germain-apm-common.aar, germain-apm-data.aar, germain-apm-library.aar
Upload all aar files into your application's
libs
folderUpdate your module level gradle file and add the following:
repositories { flatDir { dirs 'libs' } } //.... dependencies { // ... your current dependencies // ... Germain UX's 3rd party dependencies not available in the aar files (please contact Germain Support for the list) // implementation 'com.esotericsoftware:kryo:4.0.1' implementation(name:'germain-apm-common', ext:'aar') implementation(name:'germain-apm-data', ext:'aar') implementation(name:'germain-apm-library', ext:'aar') }
JAVARebuild your app and create new release after initializing our monitoring
ProGuard Integration
If your application is using ProGuard minification/obfuscation please add the following configuration settings to your proguard-rules file:
-keepclassmembers enum com.germainsoftware.apm.data.model.** { *; }
-keepattributes LineNumberTable,SourceFile
-dontwarn org.abego.treelayout.**
-dontwarn com.google.common.**
-dontwarn com.google.auto.common.**
-dontwarn me.eugeniomarletti.kotlin.**
-dontwarn org.antlr.**
-dontwarn com.esotericsoftware.kryo.**
-dontwarn org.stringtemplate.**
To disable ProGuard on Germain UX Android App Monitoring and speed up your build you can add the following configuration settings to your proguard-rules file:
-keep class com.germainsoftware.apm.** { *; }
Initialization
Initialize Germain UX monitoring in the onCreate()
method in your Application subclass. In the simplest integration two things must be provided and the rest will get autoconfigured:
Application's context
Beacon URL where the monitoring data will be sent (contact germain Team if you don't know it)Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPM.init(this, "http://GERMAIN_APM_SERVER/ingestion/fact");
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPM.init(this, "http://GERMAIN_APM_SERVER/ingestion/fact")
}
}
In addition please add the following service inside your application's AndroidManifest.xml
:
<manifest>
//....
<application>
//....
<service
android:name="com.germainsoftware.apm.mobile.library.GermainAPMService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":germainapmservice"
android:exported="false"/>
</application>
</manifest>
Configuration
In addition of the initialization and auto-configuration we allow customer to define custom configuration and enable/disable monitoring components. Please review GermainAPMConfiguration
documentation for complete list of options and setters.
Initialization with custom configuration:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
// custom config code here
// e.g. config.getApp().setName("MyAppName");
// e.g. config.setAutomaticUnhandledExceptionsMonitoring(false);
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication: Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
// custom config code here
// e.g. config.getApp().setName("MyAppName")
// e.g. config.setAutomaticUnhandledExceptionsMonitoring(false)
GermainAPM.init(this, config)
}
}
Distribution frequency
By default Germain UX monitoring sends data back to the Germain UX Server every 30 seconds (except crashes and application closure events which, if possible, we try to send as soon as they occur). You can update this frequency by setting distributionFrequency
:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
config.setDistributionFrequency(YOUR_VALUE_IN_SECONDS);
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
config.setDistributionFrequency(YOUR_VALUE_IN_SECONDS)
GermainAPM.init(this, config)
}
}
Application user
There are 2 way to associate application's user name with collected data.
During Germain UX initialization through GermainAPMConfiguration
:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
config.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE"); // department and role are optional
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
config.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE") // department and role are optional
GermainAPM.init(this, config)
}
}
On application runtime after GermainAPM initiated
:
Java:
//
GermainAPM.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE"); // department and role are optional
//
Kotlin:
//
GermainAPM.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE") // department and role are optional
//
Logging output
By default logcat logging (INFO level only) is enabled when Germain UX Library gets initiated. You can disable it completely with the following configuration:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
config.disableLogging();
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
config.disableLogging()
GermainAPM.init(this, "http://GERMAIN_APM_SERVER/ingestion/fact")
}
}
Data anonymization and exclusion
You can anonymize and/or exclude certain data fields by providing additional configuration on init via addExclusion method call on GermainAPMConfiguration
. It has the following declaration:
/**
* @param name Unique name of this exclusion
* @param fieldName Field name, example="user.name"
* @param factType Fact type this exclusion applies to. If empty, this exclusion applies to all types, example="MobileEvent"
* @param pattern Pattern to optionally match exclusion value, example="User: (.*)"
* @param preserveLength If true, will preserve length of original value when masking
* @param preserveWhitespace If true, will preserve whitespace characters when masking
* @param anonymize If true, will anonymize value
*/
public void addExclusion(String name, String fieldName, String factType, String pattern, boolean preserveLength, boolean preserveWhitespace, boolean anonymize);
Examples:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
// example of excluding user name
config.addExclusion("User Name", "user.name", null, null, true, true, false);
// example of anonynimize device id
config.addExclusion("Device Id", "device.id", null, null, false, false, true);
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
// example of excluding user name
config.addExclusion("User Name", "user.name", null, null, true, true, false)
// example of anonynimize device id
config.addExclusion("Device Id", "device.id", null, null, false, false, true)
GermainAPM.init(this, config)
}
}
Integration
Automatic crash detection for unhandled exceptions
If not disabled explicitly by setting GermainAPMConfiguration.setAutomaticUnhandledExceptionsMonitoring(false)
, detection of unhandled exceptions is automatically enabled and working once the Germain UX gets initiated. In addition we automatically collect Application Closed and Application Uptime events when a crash occurs.
Germain UX will collect the following categories of crashes if Android Os throws related unhandled exception:
Disk Write crash
Disk Read crash
Network Operation crash
Custom Slow Call
Resource Mismatch
Unbuffered IO
Cursor Leak
Closeable Leak
Activity Leak
Instance Leak
Registration Leak
File URI Exposure
Cleartext Network crash
Content URI Without Permission
Untagged Socket crash
Non SDK API Usage
If needed you can also collect a crash explicitly by calling the following code (that will also collect Application Closed and Application Uptime events so if you don't want to collect these better call GermainAPM.collectError()
or GermainAPM.collectException()
):
Java:
// ...
GermainAPM.collectCrash("CustomCrashName", e, false); // a) exception name b) Throwable content c) tell if it was an exception thrown by StrictMode or not (false means not in StrictMode policy)
// ...
Kotlin:
// ...
GermainAPM.collectCrash("CustomCrashName", e, false) // a) exception name b) Throwable content c) tell if it was an exception thrown by StrictMode or not (false means not in StrictMode policy)
// ...
Handled exception notification
You can collect an exception explicitly by adding the following code to your handled exception logic.
Simplest integration example:
Java:
try {
URL url = new URL("localhost");
} catch (MalformedURLException e) {
GermainAPM.collectException(e); // Just Throwable and we will do the rest
// application handled exception code here
}
Kotlin:
try {
URL url = new URL("localhost");
} catch (e: MalformedURLException) {
GermainAPM.collectException(e) // Just Throwable and we will do the rest
// application handled exception code here
}
Custom integration example:
Java:
try {
URL url = new URL("localhost");
} catch (MalformedURLException e) {
GermainAPM.collectException("CustomExceptionName", e, false); // More control as we can set a) exception name b) Throwable content c) tell if it was an exception thrown by StrictMode or not (false means not in StrictMode policy)
// application handled exception code here
}
Kotlin:
try {
URL url = new URL("localhost")
} catch (e: MalformedURLException) {
GermainAPM.collectException("CustomExceptionName", e, false) // More control as we can set a) exception name b) Throwable content c) tell if it was an exception thrown by StrictMode or not (false means not in StrictMode policy)
// application handled exception code here
}
Error notification
You can collect an error event explicitly by adding the following code to your code:
Java:
if(wrong_condition or value_is_wrong or an_error_occured_but_we_dont_throw_exception or ...){
GermainAPM.collectError("CustomErrorName", "CustomErrorContent");
// application logic here
}
Kotlin:
if(wrong_condition or value_is_wrong or an_error_occured_but_we_dont_throw_exception or ...){
GermainAPM.collectError("CustomErrorName", "CustomErrorContent")
// application logic here
}
System event monitoring
You can enable / disable system event monitoring by setting GermainAPMConfiguration.setSystemEventsMonitoring()
accordingly. Currently you can either monitor all of them or none. Monitoring of the below events depends on the application's permissions (e.g. if an application doesn't allow to receive calls then Germain UX monitoring won't be able to collect events on incoming calls).
List of system events monitoring can collect:
Airplane Mode
Application Changed
Application Cleared
Application First Launch
Application Fully Removed
Application Installed
Application Removed
Application Replaced
Application Replaced
Application Restarted
Application Verified
Battery Low
Battery OK
Bluetooth ACL Connected
Bluetooth ACL Disconnected
Bluetooth Device Found
Bluetooth Device Name Changed
Bluetooth Device Picker Launched
Bluetooth Device Selected
Call Outgoing
Camera Button
Configuration Changed
Device Boot
Device Docked
Device Dreaming Started
Device Dreaming Stopped
Device Reboot
Device Shut Down
Device Sleep
Device Wake Up
GTalk Connected
GTalk Disconnected
Headset
Idle Mode Changed
Input Method Changed
Locale Changed
Media Bad Removal
Media Button
Media Ejected
Media External Present
Media Incompatible FS
Media Mounted
Media Removed
Media Shared
Media Unmountable
Media Unmounted
New Picture
New Video
Phone State
Power Connected
Power Disconnected
Power Save Mode Changed
SMS Received
SMS Rejected
Storage Low
Storage Low Package
Storage OK
Timezone Changed
User ID Removed
Voicemail Fetch
Voicemail New
Wallpaper Changed
WAP Received
Wi-Fi Changed
Application event monitoring
Germain UX collects automatically the following application events:
Application startup event
Application state event (in foreground; in background)
Application low memory event (memory moderate, memory critical, memory low, memory moderate)
Application launch duration transaction
Application closed event and Application uptime duration (total, in foreground, in background)
This monitoring gets initiated automatically by Germain UX but to collect the final data points an end event must be triggered.
We distinct the following application closure scenarios which will end this monitoring:
on application crash - automatic collection triggers if "Automatic crash detection for unhandled exceptions" is enabled
on application removal from recent application list event or calling
finishAndRemoveTask()
- automatic collection triggers if "Application removal from recent application list event" monitoring is configured and enabledon application closure done programmatically(e.g. by killing its own process or by calling
finishAffinity()
) - explicit trigger is required by calling the following code:
Java:
// ...
GermainAPM.collectApplicationExit(Boolean calledFromForeground); // calledFromForeground=true if this line is called from a foreground application otherwise set it to false
// your closure code
Kotlin:
// ...
GermainAPM.collectApplicationExit(Boolean calledFromForeground) // calledFromForeground=true if this line is called from a foreground application otherwise set it to false
// your closure code
Application removal from recent application list event
This event gets collected when you swipe out monitored application from the recent application task list. Currently this monitoring is supported on SDK 25 or lower due to the limitations imposed by Android SDK26 and above.
This monitoring requires to configure the following service inside your application's AndroidManifest.xml
:
<manifest>
//....
<application>
//....
<service
android:name="com.germainsoftware.apm.mobile.library.monitoring.RecentAppRemovalListener"
android:exported="false"
android:stopWithTask="false"
/>
</application>
</manifest>
Session monitoring
Germain UX automatically generates and associates a sessionId with all data collected during a session. If you prefer to set sessionId explicitly(e.g extract session id from your application's logic) you can do that with the following code (either through configuration before Germain UX initialization either by calling the API after Germain UX initialization).
During Germain UX initialization through GermainAPMConfiguration:
Java:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact");
config.setSessionId(...YOUR_SESSION_ID...);
GermainAPM.init(this, config);
}
}
Kotlin:
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
GermainAPMConfiguration config = new GermainAPMConfiguration("http://GERMAIN_APM_SERVER/ingestion/fact")
config.setSessionId(...YOUR_SESSION_ID...)
GermainAPM.init(this, config)
}
}
On application runtime after GermainAPM initiated:
Java:
// ...
GermainAPM.setSessionId(...YOUR_SESSION_ID...);
// ...
Kotlin:
// ...
GermainAPM.setSessionId(...YOUR_SESSION_ID...)
// ...
Transaction monitoring
Germain UX allows you to monitor all kind of transactions in your application. To enable this monitoring you need to programmatically set a start of a transaction (GermainAPM.startTransaction()
) and an end of a transaction(GermainAPM.endTransaction()
). Example below shows how to use transaction monitoring for your async tasks (e.g. http request) but you can use it the same way for all other type of transactions (User transactions, Business oriented transactions. SQL transactions, ...).
Java:
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private String txnName = "Image Download"; // example of transaction name used in startTransaction() and endTransaction()
@Override
protected void onPreExecute() {
super.onPreExecute();
GermainAPM.startTransaction(txnName, "MyViewName", "ImageName"); // start your transaction (you must provide a unique transaction name and optionally view/activity name where transaction occurs and some additional information related to the transaction
// ...
}
@Override
protected Void doInBackground(Void... voids) {
// ...
try {
URL url = new URL("...");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// ...
conn.connect();
conn.getResponseMessage();
} catch (IOException e) {
e.printStackTrace();
}
// ...
return null;
}
@Override
protected void onPostExecute(Void result) {
// ...
GermainAPM.endTransaction(txnName);
}
}
Kotlin:
class MyAsyncTask() : AsyncTask<Void, Void, Void> {
var txnName: String = "Image Download"; // example of transaction name used in startTransaction() and endTransaction()
override fun onPreExecute() {
super.onPreExecute()
GermainAPM.startTransaction(txnName, "MyViewName", "ImageName") // start your transaction (you must provide a unique transaction name and optionally view/activity name where transaction occurs and some additional information related to the transaction
// ...
}
override fun doInBackground(vararg params: Void?): Void? {
// ...
try {
URL url = new URL("...")
HttpURLConnection conn = (HttpURLConnection) url.openConnection()
conn.setRequestMethod("GET")
// ...
conn.connect()
conn.getResponseMessage()
} catch (e: IOException) {
e.printStackTrace()
}
// ...
return null
}
override fun onPostExecute(result: String?) {
// ...
GermainAPM.endTransaction(txnName)
}
}
View navigation monitoring
Germain UX collects automatically duration for activity/view navigations which occur in your application. This monitoring is enabled by default for all the standard Android activities (these which extend AppCompatActivity
) which participate in Android's standard activity lifecycle.
Fragment monitoring
Germain UX collects automatically fragment (FragmentActivity
, Fragment
) rendering times. If required you can disable this monitoring by providing the following configuration during Germain UX initialization:
Java:
// ...
config.setFragmentsMonitoring(false);
// ...
Kotlin:
// ...
config.setFragmentsMonitoring(false)
// ...