An application is considered to be hybrid if it contains a WebView in the Android application.
If you use a WebView, you can use UICWebView to log request activity in the WebView. UICWebView extends the base WebView from the Android Framework, which inserts the Acoustic Experience Analytics (Tealeaf) header with the current session ID for sessionization. If you decide not to use UICWebView, then you need to customize according to the framework. See the HybridHtmlEmbeddedManual sample app for more details about setting up the Acoustic Experience Analytics (Tealeaf) bridge to send Web content to the native SDK.
Important: For each WebView defined in the hybrid application, the following requirements must be done for Replay to properly function:
- Instrument the UIC JavaScript library. See UI Capture documentation for more information.
- Instrument a JavaScript bridge between the Web and the Android native application. For more information, see Hybrid Application settings.
- Upload web content resources such as images, CSS, and static content to the Replay server. For information about uploading images to the Replay server, see Capturing images for application replay.
Google WebView auto instrumentation
Acoustic Experience Analytics (Tealeaf) Android SDK supports Hybrid applications that use Google WebView for auto instrumentation.
The SDK will set up the necessary hybrid bridge for web contents to flow to the native SDK for posting, such as registering the addJavascriptInterface method in your WebView.
Note: Target applications are hybrid applications built with Google WebView only. UI Capture SDK instrumentation should be completed before native Android SDK integration in order to support a hybrid replay experience.
Ensure the following properties are set:
- In
TeaCutsBasicConfig.properties
:GoogleWebViewEnabled=true
- In
TealeafBasicConfig.properties
: Whether the application is a single-screen hybrid application or not, useSingleScreenHybridApp=false
- In
TeaCutsAdvancedConfig.json
: If the application has a custom WebViewClient, use"ExtendsWebViewClient":false
Note: If you do extend from WebViewClient, you must use the override method for auto instrumentation to work:
public void onPageFinished(final WebView view, final String url) {
//Hook required by AspectJ to setup Tealeaf hybrid bridge
}
The following sample project demonstrates a single-screen Hybrid application (Google WebView) using auto instrumentation: AndroidRelease/Tealeaf/SampleCode/AndroidStudioProjects/HybridHtmlEmbeddedAuto
Extend android.webkit.WebView
This sample code shows how the base Android WebView is extended with UICWebView.
/*******************************************************************************
* Licensed Materials - Property of IBM
* (C) Copyright IBM Corp. 2015
* US Government Users Restricted Rights - Use, duplication or disclosure
* restricted by GSA ADP Schedule Contract with IBM Corp.
******************************************************************************/
package com.tl.uic.webkit;
import java.util.Date;
import java.util.Map;
import org.apache.http.HttpResponse;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
import com.tl.uic.TLFCache;
import com.tl.uic.Tealeaf;
import com.tl.uic.javascript.JavaScriptInterface;
import com.tl.uic.model.PropertyName;
/**
* @author ohernandezltmac
*/
public class UICWebView extends WebView {
private Date endLoad;
private Date startLoad;
private String url;
private HttpResponse httpResponse;
private Date initTime;
private long responseTime;
private long connectionInitFromSession;
private PropertyName webViewId;
/**
* @param context Android context.
*/
public UICWebView(final Context context) {
super(context);
init();
}
/**
* @param context Android context.
* @param attrs an AttributeSet passed to our parent.
*/
public UICWebView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* @param context Android context.
* @param attrs an AttributeSet passed to our parent.
* @param defStyle the default style resource ID.
*/
public UICWebView(final Context context, final AttributeSet attrs, final
int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* When page finished loading.
*
* @return When page finished loading.
*/
public final Date getEndLoad() {
return endLoad;
}
/**
* When page finished loading.
*
* @param endLoad When page finished loading.
*/
public final void setEndLoad(final Date endLoad) {
this.endLoad = endLoad;
}
/**
* When page starts loading.
*
* @return When page starts loading.
*/
public final Date getStartLoad() {
return startLoad;
}
/**
* When page starts loading.
*
* @param startLoad When page starts loading.
*/
public final void setStartLoad(final Date startLoad) {
this.startLoad = startLoad;
}
/**
* HttpResponse from the connection.
*
* @return HttpResponse from the connection.
*/
public final HttpResponse getHttpResponse() {
return httpResponse;
}
/**
* HttpResponse from the connection.
*
* @param httpResponse HttpResponse from the connection.
*/
public final void setHttpResponse(final HttpResponse httpResponse) {
this.httpResponse = httpResponse;
}
/**
* Initial time from the connection.
*
* @return Initial time from the connection.
*/
public final Date getInitTime() {
return initTime;
}
/**
* Initial time from the connection.
*
* @param initTime Initial time from the connection.
*/
public final void setInitTime(final Date initTime) {
this.initTime = initTime;
this.connectionInitFromSession = TLFCache.timestampFromSession();
}
/**
* Response time from the connection.
*
* @return Response time from the connection.
*/
public final long getResponseTime() {
return responseTime;
}
/**
* Response time from the connection.
*
* @param responseTime Response time from the connection.
*/
public final void setResponseTime(final long responseTime) {
this.responseTime = responseTime;
}
/**
* Get webview id.
*
* @return Webview id.
*/
public final PropertyName getWebViewId() {
return webViewId;
}
/**
* Set webview id.
*
* @param webviewId Webview id.
*/
public final void setWebViewId(final PropertyName webviewId) {
this.webViewId = webviewId;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("PMD.NullAssignment")
@Override
public void loadData(final String data, final String mimeType,
final String encoding) {
this.url = null;
this.initTime = null;
this.connectionInitFromSession = 0;
this.responseTime = 0;
this.httpResponse = null;
this.startLoad = new Date();
super.loadDataWithBaseURL(this.url, data, mimeType, encoding, "");
}
/**
* {@inheritDoc}
*/
@SuppressLint("NewApi")
@Override
public void loadUrl(final String url) {
loadUrl(url, null);
}
/**
* {@inheritDoc}
*/
public final void loadUrl(final String url, final Map<String, String>
extraHeaders) {
this.url = url;
Tealeaf.setTLCookie(this.url);
super.loadUrl(url, extraHeaders);
}
/**
* Initializes WebView.
*/
private void init() {
setStartLoad(new Date());
// Need it when in Eclipse editor mode
if (!this.isInEditMode()) {
this.setWebViewClient(new UICWebViewClient());
this.setWebChromeClient(new UICWebChromeClient((Activity)
this.getContext()));
this.setWebViewId(Tealeaf.getPropertyName(this));
this.addJavascriptInterface(new JavaScriptInterface(this.getContext(),
getWebViewId().getId()), "tlBridge");
}
}
/**
* Log the load time of the WebView.
*
* @return If log connection was added to queue.
*/
public final Boolean logConnection() {
final long endTime = this.getEndLoad() != null ?
this.getEndLoad().getTime() : 0;
final long startTime = this.getStartLoad() != null ?
this.getStartLoad().getTime() : 0;
final long loadTime = endTime - startTime;
return Tealeaf.logConnection(this.url, this.httpResponse,
this.connectionInitFromSession, loadTime, this.responseTime);
}
}
Extend android.webkit.WebViewClient
The sample code that follows extends the base Android WebViewClient with UICWebViewClient.
/*******************************************************************************
* Licensed Materials - Property of IBM
* (C) Copyright IBM Corp. 2015
* US Government Users Restricted Rights - Use, duplication or disclosure
* restricted by GSA ADP Schedule Contract with IBM Corp.
******************************************************************************/
package com.tl.uic.webkit;
import android.annotation.SuppressLint;
import android.os.Build;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* @author ohernandezltmac
*
*/
public class UICWebViewClient extends WebViewClient {
/**
* {@inheritDoc}
*/
@Override
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
view.loadUrl(url);
return true;
}
/**
* {@inheritDoc}
*/
@SuppressLint("NewApi")
@Override
public void onPageFinished(final WebView view, final String url) {
// Get the call back mapping string to be evaluated/loaded as
Javascript code
final String javascriptString = com.tl.uic.javascript.JavaScriptInterface.
hybridBridgeRegisterCallback();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
view.evaluateJavascript(javascriptString, null);
} else {
view.loadUrl("javascript:" + javascriptString);
}
}
}
Hybrid Application settings
This setting is for single-activty, hybrid applications. It allows the SDK to queue and post JSON messages when they are collected, instead of waiting for current activity to be in the background before sending payload to the server. It is set in the TealeafBasicConfig.properties.
Item ID | Description | Value |
---|---|---|
SingleScreenHybridApp | For a single-screen hybrid application with one Activity For example, the following frameworks need this setting to be true: Cordova, PhoneGap, and Ionic |
true |
For a hybrid application with more than one Activity For example, Google WebView auto-instrumentation |
false |
Sessionization for PhoneGap based Applications
Since there are no requests and responses being sent back to the server, Acoustic Experience Analytics (Tealeaf) does not require extending of WebView to add sessionization.
Acoustic Experience Analytics (Tealeaf) Android SDK APIs available to JavaScript
When you develop hybrid applications, you embed WebView component within a larger Android application. You can access exposed Android native methods from the UI Capture j2 global JavaScript object called "TLT" with methods that you use in your JavaScript code. This table lists and gives examples of the methods that you can include in your JavaScript code:
Method | Example |
---|---|
Enable Application Framework |
|
Disable Application Framework |
|
Log Screen Capture |
|
Start New Application Session |
|
Current Session ID |
|
Default Value for Configurable Item |
|
Value for Configurable Item |
|
Set Configurable Item |
|
Add Additional HTTP Header |
|
Log Custom Event Bridge |
|
Example of how a native Android API is started
This example shows how to start a native method to enable Acoustic Experience Analytics (Tealeaf) Framework on a UI Capture j2 "TLT" instance using JavaScript:
<script type="text/javascript">
// Example of calling the native API to enable Tealeaf Framework using Javascript
TLT.enableTealeafFramework();
</script>
Sample test HTML file that starts supported native methods in JavaScript
This example is an HTML file that starts supported native methods in JavaScript:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<script type="text/javascript" src="js/tealeaf.js"></script>
<script type="text/javascript" src="js/defaultconfiguration.js" ></script>
<title>test APIs</title>
<body>
<h2>Test page for Android bridge APIs in hybrid app</h2>
<h3>Click on below buttons to run tests:</h3>
<input type="button" style="width: 150px; height: 30px; font-size: 20px" value="Screen Capture" onclick="TLT.logScreenCapture();return false;"/>
<input type="button" style="width: 150px; height: 30px; font-size: 20px" value="Native APIs" onclick="runBridgeNativeTealeafAPIs();return false;"/>
<p/>
<p>Test native APIs output here:
<div id="queueData"></div>
</body>
<script type="text/javascript">
function htmlConsoleLog(textData, apiRetVal){
var para = document.createElement("p");
var node;
if( apiRetVal !== undefined && apiRetVal !== null )
{
node = document.createTextNode(textData + " returned: " + apiRetVal);
}
else
{
node = document.createTextNode(textData );
}
para.appendChild(node);
var element = document.getElementById("queueData");
element.appendChild(para);
}
function runBridgeNativeTealeafAPIs() {
htmlConsoleLog( '----- -------------------------------- -----' );
htmlConsoleLog( '----- Calling Tealeaf native APIs -----' );
var apiRetVal = null;
htmlConsoleLog( '----- Calling enableTealeaf -----' );
TLT.enableTealeafFramework();
apiRetVal = null;
htmlConsoleLog( '----- Calling currentSessionId -----' );
apiRetVal = TLT.currentSessionId();
htmlConsoleLog( '----- currentSessionId -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling disableTealeaf -----' );
TLT.disableTealeafFramework();
var apiRetVal = null;
htmlConsoleLog( '----- Calling enableTealeaf -----' );
TLT.enableTealeafFramework();
apiRetVal = null;
htmlConsoleLog( '----- Calling currentSessionId -----' );
apiRetVal = TLT.currentSessionId();
htmlConsoleLog( '----- currentSessionId -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling defaultValueForConfigurableItem(PostMessageUrl) -----' );
var PostMessageUrlVal = TLT.defaultValueForConfigurableItem('PostMessageUrl');
htmlConsoleLog( '----- defaultValueForConfigurableItem -----', PostMessageUrlVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling setConfigurableItem("PostMessageUrl", "aValidPostUrl") -----' );
apiRetVal = TLT.setConfigurableItem('PostMessageUrl', 'aValidPostUrl');
htmlConsoleLog( '----- setConfigurableItem -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling valueForConfigurableItem("PostMessageUrl") -----' );
apiRetVal = TLT.valueForConfigurableItem('PostMessageUrl');
htmlConsoleLog( '----- valueForConfigurableItem -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling setConfigurableItem(PostMessageUrl, ' + PostMessageUrlVal + ') -----' );
apiRetVal = TLT.setConfigurableItem('PostMessageUrl', PostMessageUrlVal );
htmlConsoleLog( '----- setConfigurableItem -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling valueForConfigurableItem("PostMessageUrl") -----' );
apiRetVal = TLT.valueForConfigurableItem('PostMessageUrl');
htmlConsoleLog( '----- valueForConfigurableItem -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling startNewTLFSession -----' );
apiRetVal = TLT.startNewTLFSession();
htmlConsoleLog( '----- startNewTLFSession -----', apiRetVal );
apiRetVal = null;
htmlConsoleLog( '----- Calling addAdditionalHttpHeader -----' );
TLT.addAdditionalHttpHeader('HeaderFromJavaScript','HeaderValueFromJavaScript');
htmlConsoleLog( '----- addAdditionalHttpHeader -----' );
apiRetVal = null;
htmlConsoleLog( '----- Calling enableTealeaf again -----' );
TLT.enableTealeafFramework();
apiRetVal = null;
htmlConsoleLog( '----- Calling currentSessionId -----' );
apiRetVal = TLT.currentSessionId();
htmlConsoleLog( '----- currentSessionId -----', apiRetVal );
apiRetVal = null;
var str = '{\'key1AndroidBridge\': \'value1AndroidBridge\', \'key2AndroidBridge\': \'value2AndroidBridge\'}';
htmlConsoleLog( '----- Calling logCustomEvent("Test Android Bridge Custom Event",' + str +') -----' );
apiRetVal = TLT.logCustomEventBridge('Test Android Bridge Custom Event', str, 0);
htmlConsoleLog( '----- logCustomEvent(Test Android Bridge Event, ' + str +' ) -----', apiRetVal );
htmlConsoleLog( '----- Done Calling Tealeaf native APIs -----' );
htmlConsoleLog( '----- -------------------------------- -----' );
htmlConsoleLog( '----- -------------------------------- -----' );
htmlConsoleLog( '----- -------------------------------- -----' );
}
</script>
</head>
</html>
Related Tutorials