Download PDF
Download page Customize the Android Instrumentation.
Customize the Android Instrumentation
Once you have instrumented your Android application with the Android SDK, you can also use the APIs exposed by the SDK to customize the data for your application that appears in the Controller UI. The following sections show you how to use the Android API to customize your instrumentation.
Because the Android Agent stores data about events in a local buffer before reporting the information, we recommend you use the APIs with discretion.
Collect Additional Types of Data
The Instrumentation
class has additional methods to allow you to extend the kinds of application data you can collect and aggregate using Mobile RUM. There are six basic kinds of extensions that you can create:
Extension | Description | Specification | Where Data is Displayed in the Controller UI |
---|---|---|---|
Info points | How often a method is invoked, and how long it takes to run. |
| |
Custom timers | Any arbitrary sequence of events within your code timed, even spanning multiple methods. |
| |
Custom metrics | Any integer-based data you wish to collect. |
| |
User data | Any string key/value pair you think might be useful. This data is included with all listed instrumentation types until it is cleared. |
| |
Breadcrumbs | The context of a crash. |
| |
User interactions | Captured when users press buttons, click on lists, and select text. |
|
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 application sends an HTTP request or when the application starts after a crash, but if custom data has been collected and neither of those events has occurred for at least 5 minutes, the custom data is sent 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 the 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 Android Agent. The first parameter to the reportMetric
call is the name you want the metric to appear under in the Controller UI. The metric name should only contain alphanumeric characters and spaces. Illegal characters are replaced by their ASCII hex value.
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.
Custom User Data
You can set and later remove any string key/value pair you think might be useful with the following methods:
setUserData(key, value, success, error)
removeUserData(key)
Once this is set, user data will continue to be sent along with any instrumentation for network requests, sessions, or crashes for the rest of the current run of the application. You can remove any previously set user data on a per-key basis. Alternatively, you can remove previously set user data for all keys at once using clearAllUserData
.
Parameters
The following table describes the parameters:
Name | Type | Description |
---|---|---|
key | string | The key identifying the key-value pair. |
value | string | The value associated with the key. |
success | function | The user-defined callback for successful cases. |
error | function | The user-defined callback for failed cases. |
Example
The code example below shows how to set user data with the SDK API:
void onUserLoggedIn(String userid) {
Instrumentation.setUserData("User ID", userid);
...
}
The code example below shows how to remove user data with the SDK API:
void onUserLoggedIn(String userid) {
Instrumentation.removeUserData("User ID", userid);
...
}
This information is available in Network Request Analyze and is added to any crash snapshot. 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 you want to capture a series of events. 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:
- Crash Reports Only
- Modal
Crash Reports Only
Using this method means that breadcrumbs are reported in crash reports only.
public static void leaveBreadcrumb(java.lang.String breadcrumb)
Modal
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:
CRASHES_ONLY
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 the Android Agent collects) available to other parts of your code, for example, to Google Analytics. 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, implement the following interface in your code:
public interface CrashReportCallback {
void onCrashesReported(Collection<CrashReportSummary> summaries);
}
The Android Agent sends a collection of crash report summaries instead of an individual callback in the event that there is more than one crash, producing multiple crash report summaries.
The method onCrashesReported
is invoked during the next initialization of the Android 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, we recommend to include all three properties: exceptionClass
and exceptionMessage
are useful for quick identification of the crash, and crashId
is useful to look up the crash in the 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 the latest JavaDocs for the complete Android SDK API.
Disable Crash Reporting
Crash reporting is enabled by default, but you can manually disable crash reporting through the instrumentation configuration. If you are using other crash reporting tools, you might disable crash reporting to minimize conflicts and optimize the crash report results.
You can disable crash reporting by configuring the instrumentation with the crashReportingEnabled
property as shown in the following:
let config = ADEumAgentConfiguration(appKey: <#EUM_APP_KEY#>);
config.crashReportingEnabled = false;
ADEumInstrumentation.initWith(config);
Report Errors and Exceptions
The string message used to construct a new throwable used for reportError
should not use unique string such as hashes, StackTraceID
or ThreadID
. Including those will create an infinite number of Error Group
which will impact performance.
To add unique error strings and values, you have two data collection options:
- Add a string value as a breadcrumb. If the event/error, you are reporting has a single value attached, add the value to
leaveBreadcrumb
. See Breadcrumbs - Add string name and integer values. If the event/error, you are reporting has both name and integer values attached, add the values to
reportMetric
. See Custom Metrics.
You can report exceptions using the method reportError
from the Instrumentation
class. Reported exceptions will appear in session details.
You can also set one of the severity levels below for an issue. With the severity level, you can filter errors in the Code Issues Dashboard or Code Issues Analyze.
ErrorSeverityLevel.INFO
ErrorSeverityLevel.WARNING
ErrorSeverityLevel.CRITICAL
The example below uses the API to report possible exceptions and sets the severity level to ErrorSeverityLevel.CRITICAL
(critical) when writing to a file.
private void writeToFile(String filePath, String data) {
try {
OutputStream outputStream = new FileOutputStream(filePath);
Writer outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write(data);
outputStreamWriter.close();
} catch (IOException e) {
Log.e("Exception", "File write failed: " + e.toString());
Instrumentation.reportError(e, ErrorSeverityLevel.CRITICAL);
}
}
Disable the Agent to Stop Sending User Data to the Collector
You can disable the agent to stop sending all data to the collector while the agent is initialized and running. For example, you can disable the agent if your app has an option for users to opt-out of monitoring for privacy reasons.
shutdownAgent
The shutdownAgent
call stops outgoing data to the collector, and does not persist data on the device.
Instrumentation.shutdownAgent();
- The call only stops the traffic out of the agent.
- Once the agent has been initialized, the call cannot be removed, and a license will have been consumed.
- If you want to make this state permanent for a device, add code in
UserDefaults
to save the state and use that flag to conditionally initialize the agent in your code.
restartAgent
To re-enable the agent and reverse shutdownAgent
, use restartAgent
.
Instrumentation.restartAgent();
- The call will respect the server side calls that can remotely shutdown the agent in a similar way.
- The call is only in effect while the app is running.
- The call will be ignored if the agent has been remotely disabled.
- If the call is removed from memory and the app restarts, or the device is rebooted, the agent will be initialized as normal.
Configure Hybrid Support
By default, the Android Agent instruments Android WebViews by injecting the JavaScript Agent into WebViews. See Hybrid Application Support for an overview and an explanation of how it works.
Runtime Configuration for Hybrid Support
The code example below disables the injection of the JavaScript Agent. If the client receives a false
for this flag, then the JavaScript Agent will be disabled. Thus, WebViews will not be instrumented, and Ajax requests will not be monitored.
Instrumentation.start(
AgentConfiguration.builder()
.withAppKey(getString(R.string.app_key))
.withContext(applicationContext)
.withJSAgentInjectionEnabled(false)
.build())
The injection occurs during the creation of a new WKWebView. So, if a WKWebView is created when this flag is set to false
, that particular WKWebView won't be instrumented even if the flag is subsequently set to true
.
The collection and reporting of Ajax calls are disabled by default. To enable the injection and the collection and reporting of Ajax calls, pass true
to the method jsAgentEnabled
in the instrumentation configuration as shown below.
Instrumentation.start(
AgentConfiguration.builder()
.withAppKey(getString(R.string.app_key))
.withContext(applicationContext)
.withJSAgentAjaxEnabled(true)
.build())
Programmatically Control Sessions
By default, a mobile session ends after a period of user inactivity. For example, when a user opens your application, the session begins and only ends after the user stops using the application for a set period of time. When the user begins to use the application again, a new session begins.
Instead of having a period of inactivity to define the duration of a session, however, you can use the following API to programmatically control when sessions begin and end:
void startNextSession()
When you call the method startNextSession
from the Instrumentation class, the current session ends and a new session begins. The API enables you to define and frame your sessions so that they align more closely with business goals and expected user flows. For example, you could use the API to define a session that tracks a purchase of a product or registers a new user.
Excessive use of this API will cause sessions to be throttled (excessive use is >10 calls per minute per Android Agent, but is subject to change). When not using the API, sessions will fall back to the default of ending after a period of user inactivity.
Example of a Programmatically Controlled Session
In the code example below, the current session ends and a new one begins when the check out is made.
public void checkoutCart(){
if (currentCartItems!=null && currentCartItems.size()>0){
CheckoutTask checkoutReq = new CheckoutTask();
checkoutReq.execute(getEndpoint() + "cart/co");
currentCartItemsMap.clear();
convertItemsMaptoList();
Instrumentation.startNextSession();
} else {
displayToast("There are no items in the cart");
}
}
Start and End Session Frames
You can use the SessionFrame
API to create session frames that will appear in the session activity. Session frames provide context for what the user is doing during a session. With the API, you can improve the names of user screens and chronicle user flows within a business context.
Use Cases
The following are common use cases for using the SessionFrame
API:
- One Activity performs multiple functions and you want more granular tracking of the individual functions.
- A user flow spans multiple activities or user interactions. For example, you could use the API to create the session frames "Login", "Product Selection", and "Purchase" to chronicle the user flow for purchases.
- You want to capture dynamic information based on user interactions to name session frames, such as an order ID.
SessionFrame API
The table below lists the three methods you can use with session frames.
Class | Method | Description |
---|---|---|
Instrumentation |
JAVA
| Use this to start and name your session frame. Naming session frames enable you to easily identify and track the frames in the Sessions Dialog. |
SessionFrame |
JAVA
| Use this to start and name your session frame. Naming session frames enable you to easily identify and track the frames in the Sessions Dialog. |
SessionFrame |
JAVA
| End the session frame. You call this method from the SessionFrame object returned from startSessionFrame . |
Session Frame Example
In the following example, the ShoppingCartActivity
class uses the SessionFrame
API to track user activity during the checkout process.
public class ShoppingCartActivity extends Activity {
SessionFrame checkoutSessionFrame;
public void onCheckoutCartButtonClicked() {
// The user starts the checkout by clicking the checkout button.
// This may be after they have updated the quantities of items in the cart, etc.
checkoutSessionFrame = Instrumentation.startSessionFrame("Checkout");
}
public void onConfirmOrderButtonClicked() {
// Once they have confirmed payment info and shipping information, and they
// are clicking the "Confirm" button to start the backend process of checking out,
// we may know more information about the order itself, such as an order ID.
checkoutSessionFrame.updateName("Checkout: Order ID " + orderId);
}
public void onProcessOrderCompleted() {
// Once the order is processed, the user is done "checking out", so we end the session frame.
checkoutSessionFrame.end();
checkoutSessionFrame = null;
}
public void onCheckoutCanceled() {
// If the user cancels or returns to the cart, you'll want to end the session frame also, or else it will be
// left open and appear to have never ended.
checkoutSessionFrame.end();
checkoutSessionFrame = null;
}
}
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
classesOkHttp
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.
Start 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 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);
}
}
Attach a Custom User Data Property to a Network Request
You can attach a custom user data property to a specific network request by adding user data to HttpRequestTracker
.
public HttpRequestTracker withUserData(String key, String value)
public HttpRequestTracker withUserLong(String key, Long value)
public HttpRequestTracker withUserBoolean(String key, Boolean value)
public HttpRequestTracker withUserDouble(String key, Double value)
public HttpRequestTracker withUserDate(String key, Date value)
Example
public byte[] sendRequest(URL url) throws HttpException {
HttpRequestTracker tracker = Instrumentation.beginHttpRequest(url);
try {
// implementation omitted
tracker.withResponseCode(theResponseCode)
.withResponseHeaderFields(theResponseHeaderFields)
.withUserData("key", "value")
.withUserLong("number", 100)
.withUserBoolean("boolean", true)
.withUserDouble("double", 1.1234)
.withUserDate("date", 1609488000)
.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 ServerCorrelationHeaders {
public static Map<String, List<String>> generate();
}
You must:
- Call the
generate
method and set the generated headers before sending a request to the backend. - Report back the response headers, using data from the
withResponseHeaderFields
field.
Override the Request/Response Content-Length
- You can generally obtain the content lengths of the network request and response by passing the headers with
HttpRequestTracker.withRequestHeaderFields()
andHttpRequestTracker.withResponseHeaderFields()
. If for some reason this does not work for your custom HTTP tracking (i.e. 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)
andHttpRequestTracker.withResponseContentLength(Long length)
.For example, you want to track a request that has a
byte
array of content. You can report the request content length by passing the size of the byte array:
byte[] requestContent;
HttpRequestTracker tracker;
tracker.withRequestContentLength(requestContent.size());
Customize the Agent Configuration
To customize the behavior of the agent itself, you pass the AgentConfiguration
object to the Instrumentation.start
method. The AgentConfiguration
object allows you to:
- Point to an on-premises EUM Server
- Enable logging
- Custom-set the application name, which is useful if you deploy the same application 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
Syntax
See the latest JavaDocs for more information.
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
.withExcludedUrlPatterns(excludedUrlPatterns) // Set excluded url regex patterns for http tracking
.withCollectorChannelFactory(collectorChannelFactory()) // The custom HTTP implementation to use
.build());
*For a list of URLs of EUM servers, see Cisco AppDynamics SaaS Domains and IP Ranges.
Configure the Agent to Use Custom HTTP Library
The Android Agent uses HTTP to deliver beacons. If you want the agent use your custom HTTP library to deliver beacons:
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; }
JAVA
This interface is loosely based onHttpURLConnection
.Implement a version of the
CollectorChannelFactory
interface: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(); }
JAVAThe implementation of
newCollectorChannel
should return a new instance of your implementation ofCollectorChannel
.Pass the
CollectorChannelFactory
to theAgentConfiguration
object.
Capture User Interactions
You can enable the 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.
You can capture when users do one or all of the following:
- Press buttons
- Select a text field
- Click a list item
Security and Privacy Concerns
The interaction capture mode is disabled by default for security and privacy reasons as user interactions may contain sensitive information. This potential security and privacy issue may be compounded if you enable both the capturing of UI interactions and screenshots.
Enable User Interaction Capture Mode
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());
Programmatically Take Screenshots
By default, mobile screenshots are enabled on the agent-side but disabled on the Controller-side. To programmatically take manual screenshots, you must enable screenshots in the Controller UI and add the following API:
Instrumentation.takeScreenshot();
For example, you can take a screenshot after loading a UI element to view how the element is displayed to your users:
@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 or with the Android SDK. To disable screenshots with the Android SDK, use the method withScreenshotsEnabled(false)
from the AgentConfiguration
class:
Instrumentation.start(AgentConfiguration.builder()
.withAppKey("<EUM_APP_KEY>")
.withContext(getApplicationContext())
.withScreenshotsEnabled(false)
.build());
}
Block/Unblock Screenshots
You can also use the Android SDK to block screenshots from being taken during the execution of a code block. This just temporarily blocks screenshots from being taken until you unblock screenshots. This enables you to stop taking screenshots in situations where users are entering personal data, such as on login and account screens.
You use the methods Instrumentation.blockScreenshots()
and Instrumentation.unblockScreenshots()
to block and unblock screenshots. If screenshots are disabled through AgentConfiguration.Builder.withScreenshotsEnabled(true)
or through the Controller UI, these methods have no effect. You can call Instrumentation.screenshotsBlocked()
to check if screenshots are being blocked.
The following example demonstrates how you could use the API to block and unblock screenshots when displaying user information.
public class UserAccountActivity extends Activity {
...
public void displayCustomerAccount() {
// Check to see if screenshots are blocked
if (! Instrumentation.screenshotsBlocked()) {
// If screenshots aren't blocked, block them before showing customer details
Instrumentation.blockScreenshots();
}
// Code to display customer details
displayAccount(this.user);
// After you're done, unblock screenshots
Instrumentation.unblockScreenshots();
}
...
}
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:
- Implement a network request callback that modifies or ignores specific URLs.
- Register the network request callback in the initialization code.
Implement the Network Request Callback
The callback that modifies or ignores specific URLs is an implementation of the interface below. The method onNetworkRequest
is synchronous, we recommend 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:
- Identify specific URLs using techniques such as regex or pattern matching.
- Modify the
url
property of theHttpRequestTracker
object. - Assign a valid URL to the
url
property. Modifying other properties of theHttpRequestTracker
object will be ignored. - 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. For 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://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:
Identify specific URLs using techniques such as regex or pattern matching.
- Return
false
.
You could also 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, you would identify network requests that you didn't want to monitor and return false
to ignore the network request. For 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. When the Android Agent is ready to create a network request beacon, it will first call the callback with an HttpRequestTracker
object.
To register the callback in the initialization code:
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. Make 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 Documentation
For the complete SDK API documentation, see the latest JavaDocs or the previous versions listed below:
- https://sdkdocs.appdynamics.com/android-sdk/22.8/
- https://sdkdocs.appdynamics.com/android-sdk/22.2/
- https://sdkdocs.appdynamics.com/android-sdk/22.1/
- https://sdkdocs.appdynamics.com/android-sdk/21.11/
- https://sdkdocs.appdynamics.com/android-sdk/21.6/
- https://sdkdocs.appdynamics.com/android-sdk/21.5/
- https://sdkdocs.appdynamics.com/android-sdk/21.2/
- https://sdkdocs.appdynamics.com/android-sdk/20.11/
- https://sdkdocs.appdynamics.com/android-sdk/20.10/
- https://sdkdocs.appdynamics.com/android-sdk/20.7/
- https://sdkdocs.appdynamics.com/android-sdk/20.5/
- https://sdkdocs.appdynamics.com/android-sdk/20.3/