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"/>
CODE

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$'
            }
        }
    }
}
JAVA

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')
}
JAVA

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$'
            }
        }
    }
}
JAVA

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>
JAVA

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 folder 

  • Update 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')
    }
    JAVA
  • Rebuild 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.**
JAVA

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.** { *; }
JAVA

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");
    }

}
JAVA

Kotlin:

class SampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    GermainAPM.init(this, "http://GERMAIN_APM_SERVER/ingestion/fact")
  }

}
JAVA

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>
JAVA

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);
    }

}
JAVA

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)
  }

}
JAVA

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);
    }

}
JAVA

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)
  }

}
JAVA

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);
    }

}
JAVA

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)
  }

}
JAVA

On application runtime after GermainAPM initiated:

Java:

	//
	GermainAPM.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE"); // department and role are optional
	//
JAVA

Kotlin:

	//
	GermainAPM.setUser("USER_NAME","USER_DEPARTMENT", "USER_ROLE") // department and role are optional
	//
JAVA

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);
    }

}
JAVA

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")
  }

}
JAVA

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);
JAVA

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);
    }

}
JAVA

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)
  }

}
JAVA

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)
// ...
JAVA

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)
// ...
JAVA

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
}
JAVA

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
}
JAVA

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
}
JAVA

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
}
JAVA

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
}
JAVA

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
}
JAVA

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 enabled

  • on 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
JAVA

Kotlin:

// ...
GermainAPM.collectApplicationExit(Boolean calledFromForeground) // calledFromForeground=true if this line is called from a foreground application otherwise set it to false
// your closure code
JAVA

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>
JAVA

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);
    }

}
JAVA

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)
  }

}
JAVA

On application runtime after GermainAPM initiated:

Java:

// ...
GermainAPM.setSessionId(...YOUR_SESSION_ID...);
// ...
JAVA

Kotlin:

// ...	
GermainAPM.setSessionId(...YOUR_SESSION_ID...)
// ...
JAVA

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);
    }
}
JAVA

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)
    }
}
JAVA

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);
// ...
JAVA

Kotlin:

// ...	
config.setFragmentsMonitoring(false)
// ...
JAVA