PDFs


This page applies to an earlier version of the AppDynamics App IQ Platform.
For documentation on the latest version, see the 4.4 Documentation.


On this page:

Related pages:

Your Rating:
Results:
PatheticBadOKGoodOutstanding!
59 rates

Once you have instrumented your Android application with the Mobile Android SDK, you can also use the APIs exposed by the SDK to customize the data for your app that appears in the Controller UI. 

See the JavaDocs

See the latest JavaDocs or the previous versions listed below:

Extend the Mobile Agent

You can use methods available in the Instrumentation class to collect five additional types of data.

When you have set up info points, custom timers, custom metrics, and/or user data, the Mobile Agent packages that data in a mobile beacon. Normally, the beacon is transmitted when the instrumented app sends an HTTP request or when the app is restarted following a crash, but if custom data has been collected and neither of those events have occurred for at least 5 minutes, the custom data is sent on at that time.

Info Points

Information points allow you to track how your own code is running. You can see how often a method is invoked, how long it takes to run, and if an exception is thrown. The simplest way to set up an information point is to use the  @InfoPoint annotation. For example:

@InfoPoint
 public void infoPointMethod(String arg1, int arg2, long value) {
   System.out.println("Executing infoPointMethod!");
 }

You can also do this manually, using the CallTracker interface. For example, to collect information on your downloadImage method, you could use code similar to this:

private void downloadImage(URL url) {
     CallTracker tracker =
          Instrumentation.beginCall("com.example.android.awesomeapp.ImageDownloader", "downloadImage")
              .withArguments(url);
     try {
          //download image.
          tracker.reportCallEnded()
     } catch(Exception e) {
         //handle exception thrown
         tracker.reportCallEndedWithException(e);
     }
 }

This information appears in the Custom Data view of the Controller UI.

Custom Timers

Custom timers allow you to time any arbitrary sequence of events within your code, even spanning multiple methods, by using startTimer and stopTimer.

public class MyActivity extends Activity {
   @Override
   protected void onStart(){
       Instrumentation.startTimer("Time Spent on MyActivity");
       //your code here.
   }

   @Override
   protected void onStop(){
       Instrumentation.stopTimer("Time Spent on MyActivity");
       //your code here.
   }
 }

 The methods startTimer(String) and stopTime(String) can be called from different threads. Calling startTimer again with the same name value resets a named timer.

This information appears in the Custom Data view of the Controller UI.

Custom Metrics

Any integer-based data can be passed to the agent. The first parameter to the report.Metric call is the name you want the metric to appear under in the Controller UI. For example, to track the number of times your users click the checkout button in your UI, you could use code similar to this.

findViewById(R.id.checkout_button).setOnClickListener(new View.OnClickListener(){
      @Override
      public void onClick(View view){
          //run your checkout routine.
          Instrumentation.reportMetric("Checkout Count", 1);
      }
 });

This information appears in the Custom Data view of the Controller UI.

User Data

You can set any string key/value pair you think might be useful. The first parameter to the setUserData call is the key you want to use, which must be unique across your application. The second is the value you want assigned to the key.

For example: 

void onUserLoggedIn(String userid) {
    Instrumentation.setUserData("User ID", userid); 
    ...
}

This information is available in Network Request Analyze and is added to any crash snapshots that may be taken. Keys and values are limited to 2048 characters each.

You can also set user data with values of other types (Long, Boolean, Double, Date) using the following methods:

Breadcrumbs

Breadcrumbs allow you to situate a crash in the context of your user's experience. Set a breadcrumb when something interesting happens. If your application crashes at some point in the future, the breadcrumb will be displayed along with the crash report.

There are two ways of leaving breadcrumbs:

Using this method means that breadcrumbs are reported in crash reports only.

public static void leaveBreadcrumb(java.lang.String breadcrumb)
Using this method lets you fine tune where the breadcrumbs are reported, either only in crash reports or in crash reports and sessions.
public static void leaveBreadcrumb(java.lang.String breadcrumb, int mode)

Where mode is either:

  • 0 for CRASHES_ONLY
  • 1 for CRASHES_AND_SESSIONS.

If the  breadcrumb is over 2048 characters, it is truncated.  If it is empty, no breadcrumb is recorded.  Each crash report displays the most recent 99 breadcrumbs.

 

Add a Crash Reporting Callback

You may want to make crash report information that Mobile RUM collects available to other parts of your code, for example, to Google Analytics, if you are using it. To enable passing on summary crash information, you can set up a crash report runtime callback. To get a callback when the Android Agent detects and then reports a crash, you need to implement the following interface in your code:

public interface CrashReportCallback {
    void onCrashesReported(Collection<CrashReportSummary> summaries);
}

We send a Collection instead of an individual callback because there could be more than 1 crash, even though there typically is only one. 

The method onCrashesReported is invoked during the next initialization of the agent after a crash has occurred.

This callback is invoked on your app's UI thread, so any work should be done on a separate work thread.

Each CrashReportSummary has the following properties:

public class CrashReportSummary {
    public final String crashId;
    public final String exceptionClass;
    public final String exceptionMessage;
}

If you are sending the information to another analytics tool, such as Google Analytics, it is best to include all three properties: exceptionClass and exceptionMessage are useful for a quick identification of what the crash is, but for more complete information, crashId can be used to look up the crash in the AppDynamics Controller UI.

For example, to print the crash information to Android's logger, you could implement a CrashReportCallback class like this:

public static class MyCrashReportCallback implements CrashReportCallback {
    @Override
    public void onCrashesReported(Collection<CrashReportSummary> summaries) {
        for (CrashReportSummary crash : summaries) {
            Log.e("MyApp", "Crash Detected: " + crash.exceptionClass + " : " + crash.exceptionMessage + " (" + crash.crashId + ")");
        }
    }
}

You set your callback as using the AgentConfiguration object:

final AgentConfiguration config = AgentConfiguration.builder()
        .withAppKey(appKey)
        .withContext(context)
        .withCrashCallback(new MyCrashReportCallback())
        .build();

Your callback is invoked after a crash, during the next initialization, on the main thread. For more information, see latest JavaDocs for the complete Android SDK API.

Use a Custom HTTP Library

The Android Agent automatically detects network requests when the underlying implementation is handled by any one of the supported network libraries. To have the Android Agent detect requests from a custom library, add request tracking code to your application manually, using the HttpRequestTracker interface.  

Supported Network Libraries

The libraries below cover the great majority of Android network requests. In some cases, however, mobile applications use custom HTTP libraries.  

  • HttpURLConnection
  • HttpsURLConnection
  • HttpClient classes
  • OkHttp
  • OkHttp3
  • ch.boye.httpclientandroidlib

To set headers to allow correlation with server-side processing, use the ServerCorrelationHeaders class.  

Add Request Tracking

To add request tracking manually, you use an HttpRequestTracker object to tell the agent when the request begins and when it ends and to report fields of the response to the agent.

Tracking a request

To begin tracking an HTTP request, use an instance of the following interface.

You must initialize the agent using the Instrumentation.start method before using this interface.

public interface HttpRequestTracker {
    public Exception getException();
    public HttpRequestTracker withException(Exception e);
 
    public String getError();
    public HttpRequestTracker withError(String error);
 
    public int getResponseCode();
    public HttpRequestTracker withResponseCode(int responseCode);
 
    public Map<String, List<String>> getResponseHeaderFields();
    public HttpRequestTracker withResponseHeaderFields(Map<String, List<String>> responseHeaderFields);
 
   /**
    * Stops tracking an HTTP request.
    *
    * Immediately after receiving a response or an error, set the appropriate fields and call this method to
    * report the outcome of the HTTP request. You should not continue to use this object after calling this
    * method -- if you need to track another request, obtain a new instance.
    */
    public void reportDone();
}

Example:

Given a request snippet like this:

public byte[] sendRequest(URL url) throws HttpException {
    try {
        // implementation omitted
        return responseBody;
    } catch (UnderlyingException e) {
        throw new HttpException(e);
    }
}

Adding the tracker could look something like this:

public byte[] sendRequest(URL url) throws HttpException {
    HttpRequestTracker tracker = Instrumentation.beginHttpRequest(url);
    try {
        // implementation omitted
        tracker.withResponseCode(theResponseCode)
               .withResponseHeaderFields(theResponseHeaderFields)
               .reportDone();
        return responseBody;
    } catch (UnderlyingException e) {
        tracker.withException(e)
               .reportDone();
        throw new HttpException(e);
    }
}

Enable Server-Side Correlation

To enable correlation between your request and server-side processing, add specific headers to outgoing requests that the server-side agent can detect.

This is done automatically for standard HTTP libraries.

public class ServerCorreleationHeaders {
    public static Map<String, List<String>> generate();
}

You must:

  1. Call the generate method and set the generated headers before sending a request to the backend.
  2. Report back the response headers, using data from the withResponseHeaderFields field.

Override the Request/Response Content Length

You can generally obtain the the content lengths of the network request and response by passing the headers with HttpRequestTracker.withRequestHeaderFields() and HttpRequestTracker.withResponseHeaderFields().

If for some reason this does not work for your custom HTTP tracking—for example, the network library doesn't populate those fields until its being transmitted—then you can still report the request and response content lengths using HttpRequestTracker.withRequestContentLength(Long length) and HttpRequestTracker.withResponseContentLength(Long length).

For example, suppose you want to track a request that has a byte array of content. You could report the request content length by passing the size of the byte array as shown below.

byte[] requestContent;
HttpRequestTracker tracker;
tracker.withRequestContentLength(requestContent.size());
Use the AgentConfiguration Object to Customize the Agent

To customize the behavior of the agent itself, you pass the AgentConfiguration object to the Instrumentation.start method. The AgentConfiguration object allows you to do these things:

  • Point to an on-premises EUM Server
  • Enable logging
  • Custom set the application name, useful if you deploy essentially the same app binary with different package names to different geographic areas. This ensures that all the data ends up being processed under the same name.
  • Ignore HTTP requests internal to your application that are not used for network requests
  • Configure the agent to use your custom HTTP library to send its beacons

The syntax looks like the following:

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withCollectorURL(collectorURL) // The URL of the EUM Server(on-prem)
      .withCompileTimeInstrumentationCheck(true) // Set to false if you are using features of the SDK only, like custom HTTP support, but not to instrument your app.      
      .withLoggingEnabled(true)//set default INFO logging. Tagged "AppDynamics".
      .withApplicationName(applicationName)//set a custom app name
	  .withwithExcludedUrlPatterns(excludedUrlPatterns) // Set excluded url regex patterns for http tracking
      .withCollectorChannelFactory(collectorChannelFactory()) // The custom HTTP implementation to use
      .build());

See the latest JavaDocs for more information.

 

Configure the Agent to Use Custom HTTP Library

The Android Agent uses HTTP to deliver its beacons. To have the agent use your custom HTTP library for this purpose, do the following.

  1. Implement a class that extends the following abstract class:

    public abstract class CollectorChannel {
        private URL url;
        private int connectTimeout;
        private int readTimeout;
        private Map<String, List<String>> requestProperties = new HashMap<String, List<String>>();
        private String requestMethod;
     
        public void setURL(URL url) {
            this.url = url;
        }
     
        public URL getURL() {
            return url;
        }
     
        public void setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
        }
     
        public int getConnectTimeout() {
            return connectTimeout;
        }
     
        public void setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
        }
     
        public int getReadTimeout() {
            return readTimeout;
        }
     
        public void addRequestProperty(String property, String value) {
            if (!requestProperties.containsKey(property)) {
                requestProperties.put(property, new ArrayList<String>());
            }
            requestProperties.get(property).add(value);
        }
     
        public Map<String, List<String>> getRequestProperties() {
            return Collections.unmodifiableMap(requestProperties);
        }
     
        public void setRequestMethod(String requestMethod) {
            this.requestMethod = requestMethod;
        }
     
        public String getRequestMethod() {
            return requestMethod;
        }
     
        public abstract OutputStream getOutputStream() throws IOException;
     
        public abstract InputStream getInputStream() throws IOException;
     
        public abstract int getResponseCode() throws IOException;
     
        public abstract Map<String,List<String>> getHeaderFields() throws IOException;
    }

    This interface is loosely based on HttpURLConnection.

  2. Implement a version of the CollectorChannelFactory interface, which looks like this:

    public interface CollectorChannelFactory {
    
        /**
         * Returns a new instance of CollectorChannel.
         *
         * If you want to supply a custom CollectorChannel, implement this interface, and return
         * an instance of your concrete implementation of CollectorChannel from this method.
         */
        public CollectorChannel newCollectorChannel();
    }

    The implementation of newCollectorChannel should return a new instance of your implementation of CollectorChannel.

  3. Pass the CollectorChannelFactory to the AgentConfiguration object.

Enable User Interaction Capture Mode

You can enable the Android Agent to track certain user interactions. Once user interactions have been captured, you can sort sessions by UI event and view the UI event in the timeline of the session waterfall. 

The interaction capture mode is disabled by default for security and privacy reasons as user interactions may contain sensitive information.

You can capture when users do one or all of the following:

  • press buttons
  • select a text field
  • click on a list item

To enable user interaction capture mode, pass the capture mode to the method withInteractionCaptureMode() from an AgentConfiguration object. The instrumentation code example below configures the Android Agent to capture all the supported types of user interactions.

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withInteractionCaptureMode(InteractionCaptureMode.All)
      .build());

You can also configure the Android Agent to only capture one type of user interaction:

Instrumentation.start(AgentConfiguration.builder()
      .withAppKey("<EUM_APP_KEY>")
      .withContext(getApplicationContext())
      .withInteractionCaptureMode(InteractionCaptureMode.ButtonPressed)
      .build());

Manually Take Screenshots

Mobile screenshots are enabled by default in the Android Agent. You can configure the Controller UI to automatically take screenshots or use the Android SDK to manually take a screenshot as shown below:

Instrumentation.takeScreenshot(); 

For example, you might want to take a screenshot after you load a UI element to view how it's displayed to customers:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_spinner_with_toast);
   spinner = (Spinner) findViewById(R.id.spnOptions); 
   btnSpinnerVal = (Button) findViewById(R.id.btnSpinnerValue);
   loadSpinner();
   Instrumentation.takeScreenshot();
}

Disable Screenshots

 You can disable screenshots from the Controller UI (TBD: link to doc) or with the Android SDK. To disable screenshots with the Android SDK, use the method screenshotsEnabled(false) from the AgentConfiguration class as shown below.

Instrumentation.start(AgentConfiguration.builder()
     .withAppKey("ABC-DEF-GHI")
     .withContext(getApplicationContext())
     .screenshotsEnabled(false)
     .build());
}

Transform URLs for Network Requests

When your application makes network requests, you may not want to report URLs containing sensitive information to the EUM Server. You can instead transform the network request URL before reporting it or ignore it altogether. 

To do so:

  1. Implement a network request callback that modifies or ignores specific URLs. 
  2. Register the network request callback in the initialization code.

Implement the Network Request Callback

The callback that modifies or ignore specific URLs is an implementation of the interface below. The method onNetworkRequest is synchronous, so it is recommended that you return from the function quickly.

public interface com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    boolean onNetworkRequest(HttpRequestTracker httpRequestTracker);
}

 Transforming URLs

The onNetworkRequest method, in general, should follow the steps below to transform URLs:

  1. Identify specific URLs using techniques such as regex or pattern matching.
  2. Modify the url property of the HttpRequestTracker object.
  3. Assign a valid URL to the url property. (Modifying other properties of the HttpRequestTracker object will be ignored.)
  4. Return true.

The first step is optional as you could choose to transform the URLs of all network requests. 

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        URL urlMask = new URL("http://networkrequest-mask.com");
        httpRequestTracker.withURL(urlMask);
        return true;
    }
}

In general, however, you would want to identify and transform URLs that contain sensitive information as implied in the example below.

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        String urlString = httpRequestTracker.getURL().toString();
        try {
            URL url = new URL("http://customer-account.com");
            if (urlString.contains("accountInfo")) {
                // Change the URL for calls to Facebook
                httpRequestTracker.withURL(url);
                return true;
            } 
        } catch (MalformedURLException e) {
            return false;
        }
        return true;
    }
}

Ignoring URLs

If the onNetworkRequest method returns false, the beacon is dropped. The general process for ignoring beacons is as follows:

  1. Identify specific URLs using techniques such as regex or pattern matching.

  2. Return false.

You could theoretically ignore all network requests with the following implementation of onNetworkRequest.

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        return false;
    }
}

In general though, you would identify network requests that you didn't want to monitor and return false to ignore the network request as implied by this example.

private static class myNetworkRequestCallback implements com.appdynamics.eumagent.runtime.NetworkRequestCallback  {
    @Override
    public boolean onNetworkRequest(HttpRequestTracker httpRequestTracker) {
        String urlString = httpRequestTracker.getURL().toString();
        try {
            URL url = new URL("http://socialnetworksite.com");
            if (urlString.contains("avatar")) {
                // Ignore calls for avatars
                return false;
            }
        } catch (MalformedURLException e) {
            return false;
        }
        return true;
    }
}

Register the Network Request Callback

After implementing the callback, you register it in the initialization code as shown below. When the Android Agent is ready to create a network request beacon, it will first call the callback with an HttpRequestTracker object.

Instrumentation.start(AgentConfiguration.builder()
        .withContext(getApplicationContext())
        .withAppKey("<EUM_APP_KEY>")
        .withNetworkRequestCallback(new myNetworkRequestCallback())
        .build());

Enable Logging and Set Logging Level

You use the method withLoggingLevel of the class AgentConfiguration to enable logging and set the logging level. You can set logging to one of the following levels:

  • LOGGING_LEVEL_NONE
  • LOGGING_LEVEL_INFO
  • LOGGING_LEVEL_VERBOSE

Use verbose logging only for troubleshooting and be sure to disable for production.

Example:

AgentConfiguration config = AgentConfiguration.builder()
config.withAppKey(appKey)
config.withContext(context)
 .withLoggingLevel(Instrumentation.LOGGING_LEVEL_VERBOSE)
        .build();
    Instrumentation.start(config);

Android SDK Deprecations for 4.3

The following sections list the deprecations for the Android SDK.  

class AgentConfiguration

The following methods have been deprecated:

  • withDynamicInfoPointURL(String) -The dynamic info point feature has been removed, so this method will do nothing. 
  • withLoggingEnabled(boolean loggingEnabled) - This enables logging for the instrumentation. The default logging level is Instrumentation.LOGGING_LEVEL_INFO. Use withLoggingLevel to enable logging and set the logging level.

class Instrumentation

The following methods have been deprecated:

  • start(String appKey, Context context, String collectorURL)
  • start(String appKey, Context context, boolean debugEnabled)
  • start(String appKey, Context context, String collectorURL, boolean debugEnabled)
  • setUserData(String key, String value, boolean persist) - The ability to persist user data has been deprecated.

class HttpRequestTracker

The following method has been deprecated:

  • withException(Exception e)