Skip to content

Android Application Integration


By collecting Metrics data from Android applications, analyze application performance visually.

Prerequisites

Note

If the RUM Headless service has been activated, the prerequisites are automatically configured, and the application can be directly integrated.

Application Integration

  1. Go to User Access Monitoring > Create Application > Android;
  2. Enter the application name;
  3. Enter the application ID;
  4. Select the application integration method:

    • Public DataWay: Directly receives RUM data without installing the DataKit collector.
    • Local environment deployment: Receives RUM data after meeting the prerequisites.

Installation

Source Code: https://github.com/GuanceCloud/datakit-android

Demo: https://github.com/GuanceDemo/guance-app-demo

Gradle Configuration

  • Add the SDK remote repository address in the root directory's build.gradle file
buildscript {
    //...
    repositories {
        //...
        //Add the SDK remote repository address
        maven {
            url 'https://mvnrepo.jiagouyun.com/repository/maven-releases'
        }
    }
    dependencies {
        //...
        //Add the Plugin dependency, requires AGP 7.4.2 or above, Gradle 7.2.0 or above
        classpath 'com.cloudcare.ft.mobile.sdk.tracker.plugin:ft-plugin:[latest_version]'
        // For AGP versions below 7.4.2, use ft-plugin-legacy 
        //classpath 'com.cloudcare.ft.mobile.sdk.tracker.plugin:ft-plugin-legacy:[latest_version]'
    }
}
allprojects {
    repositories {
        //...
        //Add the SDK remote repository address
        maven {
            url 'https://mvnrepo.jiagouyun.com/repository/maven-releases'
        }
    }
}
//setting.gradle
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
        //Add the SDK remote repository address
        maven {
            url('https://mvnrepo.jiagouyun.com/repository/maven-releases')
        }
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        //Add the SDK remote repository address
        maven {
            url('https://mvnrepo.jiagouyun.com/repository/maven-releases')
        }
    }
}

//build.gradle
plugins{
    //Add the Plugin dependency, requires AGP 7.4.2 or above, Gradle 7.2.0 or above
    id 'com.cloudcare.ft.mobile.sdk.tracker.plugin' version '[lastest_version]' apply false
    // For AGP versions below 7.4.2, use ft-plugin-legacy 
    //id 'com.cloudcare.ft.mobile.sdk.tracker.plugin.legacy' version '[lastest_version]' apply false
}
  • Add the SDK dependency and Plugin usage in the main module app's build.gradle file.
dependencies {
    //Add the SDK dependency
    implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-sdk:[latest_version]'
    //Dependency for capturing native layer crash information, must be used with ft-sdk, cannot be used alone
    implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-native:[latest_version]'
    // json serialization
    implementation 'com.google.code.gson:gson:2.8.+'
    //Optional, required if automatic network request collection and automatic link tracing are needed, minimum compatible version 3.12.+
    implementation 'com.squareup.okhttp3:okhttp:4.+'
}

//Apply the plugin in the app build.gradle, missing configuration will affect the following automatic collection functions.
// 
// Plugin` configuration functions: 
// * Automatic configuration collection: App startup, OkHttp requests, WebView activities, Activity navigation 
// * Automatic collection of View click events, Console Logcat 
// * Automatic collection of Console Logcat 
apply plugin: 'ft-plugin'  //If using ft-plugin-legacy, use this configuration as well

//Configure plugin usage parameters (optional)
FTExt {
    //Whether to show Plugin logs, default is false
    //showLog = true

    //Set ASM version, supports asm7 - asm9, default is asm9
    //asmVersion='asm7'

    //ASM ignore path configuration, . and / are equivalent in paths
    //ignorePackages=['com.ft','com/ft']
}
android{
    //...omitted code
    defaultConfig {
        //...omitted code
        ndk {
            //When using ft-native to capture native layer crash information, select the supported abi architecture based on the application's platform
            //Currently, ft-native includes the following abi architectures: 'arm64-v8a',
            // 'armeabi-v7a', 'x86', 'x86_64'
            abiFilters 'armeabi-v7a'
        }
    }
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
}

For the latest version, refer to the version names of ft-sdk, ft-plugin, and ft-native above

Application Configuration

The best place to initialize the SDK is in the onCreate method of Application. If your application does not yet have an Application, you need to create one and declare it in AndroidManifest.xml. Refer to the example here.

<application 
       android:name="YourApplication"> 
</application> 

R8 / Proguard Obfuscation Configuration

If minifyEnabled = true is set in android.buildTypes, the following configuration needs to be enabled:

-dontwarn com.ft.sdk.**

### ft-sdk library
-keep class com.ft.sdk.**{*;}

### ft-native library
-keep class ftnative.*{*;}

### Prevent Action class names from being obfuscated in action_name ###
-keepnames class * extends android.view.View
-keepnames class * extends android.view.MenuItem

SDK Initialization

Basic Configuration

public class DemoApplication extends Application {

    @Override
    public void onCreate() {
        //Local environment deployment, Datakit deployment
        FTSDKConfig config = FTSDKConfig.builder(datakitUrl);
        //Use public DataWay
        FTSDKConfig config = FTSDKConfig.builder(datawayUrl, clientToken);
        //...
        //config.setDebug(true);                //debug mode
        config.setCompressIntakeRequests(true); //Compress intake data
        FTSdk.install(config);
    }
}
class DemoApplication : Application() {
    override fun onCreate() {
        //Local environment deployment, Datakit deployment
        val config = FTSDKConfig.builder(datakitUrl)
        //Use public DataWay
        val config = FTSDKConfig.builder(datawayUrl, clientToken)
        //...
        //config.setDebug(true);                //debug mode
        config.setCompressIntakeRequests(true)  //Compress intake data
        FTSdk.install(config)
    }
}
Method Name Type Required Meaning
datakitUrl String Yes Local environment deployment (Datakit) intake URL, example: http://10.0.0.1:9529, default port 9529, the device installing the SDK must be able to access this address. Note: Choose either datakitUrl or datawayUrl
datawayUrl String Yes Public DataWay intake URL, obtained from the [User Access Monitoring] application, example: https://open.dataway.url, the device installing the SDK must be able to access this address. Note: Choose either datakitUrl or datawayUrl
clientToken String Yes Authentication token, must be configured with datawayUrl
setDebug Boolean No Whether to enable debug mode. Default is false, enabling it will print SDK runtime logs
setEnv EnvType No Set the intake environment, default is EnvType.PROD,
setEnv String No Set the intake environment, default is prod. Note: Only one of String or EnvType needs to be configured
setOnlySupportMainProcess Boolean No Whether to only support running in the main process, default is true, set to false if needed in other processes
setEnableAccessAndroidID Boolean No Enable obtaining Android ID, default is true, set to false to stop collecting device_uuid field data, related to market privacy audits see here
addGlobalContext Dictionary No Add SDK global attributes, add rules see here
setServiceName String No Set the service name, affects the service field data in Log and RUM, default is df_rum_android
setAutoSync Boolean No Whether to automatically sync data to the server after collection, default is true. When false, use FTSdk.flushSyncData() to manage data sync manually
setSyncPageSize Int No Set the number of entries per sync request, SyncPageSize.MINI 5 entries, SyncPageSize.MEDIUM 10 entries, SyncPageSize.LARGE 50 entries, default is SyncPageSize.MEDIUM
setCustomSyncPageSize Enum No Set the number of entries per sync request, range [5,), note that a larger number of entries means more computational resources for data sync, default is 10 Note: Only one of setSyncPageSize or setCustomSyncPageSize needs to be configured
setSyncSleepTime Int No Set the sync interval time, range [0,5000], unit ms, default is 0
enableDataIntegerCompatible Void No Recommended to enable when coexisting with web data. This configuration handles web data type storage compatibility issues. Enabled by default in version 1.6.9
setNeedTransformOldCache Boolean No Whether to be compatible with old cache data from ft-sdk versions below 1.6.0, default is false
setCompressIntakeRequests Boolean No Compress intake sync data with deflate, default is off, supported in ft-sdk 1.6.3 and above
enableLimitWithDbSize Void No Enable limiting data size with db, default 100MB, unit Byte, larger database means more disk pressure, default is off.
Note: After enabling, FTLoggerConfig.setLogCacheLimitCount and FTRUMConfig.setRumCacheLimitCount will be invalid. Supported in ft-sdk 1.6.6 and above
setEnableOkhttpRequestTag bool No Automatically add unique ResourceID to Okhttp Request for high concurrency scenarios with the same request. Supported in ft-sdk 1.6.10 and above, ft-plugin 1.3.5 and above
setProxy java.net.Proxy No Set Proxy for data network sync requests, only supports okhttp3, supported in ft-sdk 1.6.10 and above
setProxyAuthenticator okhttp3.Authenticator No Set Proxy Authenticator for data network sync requests, only supports okhttp3, supported in ft-sdk 1.6.10 and above
setDns okhttp3.Dns No Custom Dns for data network sync requests, only supports okhttp3, supported in ft-sdk 1.6.10 and above
setDataModifier DataModifier No Modify a single field. Supported in ft-sdk 1.6.11 and above, example usage see here
setLineDataModifier LineDataModifier No Modify a single line of data. Supported in ft-sdk 1.6.11 and above, example usage see here
setRemoteConfiguration Int No Whether to enable remote configuration for data collection, default is false. When enabled, SDK initialization or application hot start will trigger data updates. Supported in ft-sdk 1.6.12 and above. Requires datakit version >=1.60 or using public dataway
setRemoteConfigMiniUpdateInterval Int No Set the minimum interval for data updates, unit seconds, default is 12 hours. Supported in ft-sdk 1.6.12 and above

RUM Configuration

FTSdk.initRUMWithConfig(
        new FTRUMConfig()
            .setRumAppId(RUM_APP_ID)
            .setEnableTraceUserView(true)
            .setDeviceMetricsMonitorType(DeviceMetricsMonitorType.ALL.getValue())
            .setEnableTraceUserAction(true)
            .setEnableTraceUserResource(true)
            .setEnableTrackAppUIBlock(true)
            .setEnableTrackAppCrash(true)
            .setEnableTrackAppANR(true)
            .setExtraMonitorTypeWithError(ErrorMonitorType.ALL.getValue())
);
FTSdk.initRUMWithConfig(
            FTRUMConfig()
                .setRumAppId(RUM_APP_ID)
                .setEnableTraceUserView(true)
                .setDeviceMetricsMonitorType(DeviceMetricsMonitorType.ALL.getValue())
                .setEnableTraceUserAction(true)
                .setEnableTraceUserResource(true)
                .setEnableTrackAppUIBlock(true)
                .setEnableTrackAppCrash(true)
                .setEnableTrackAppANR(true)
                .setExtraMonitorTypeWithError(ErrorMonitorType.ALL.getValue())
        )
Method Name Type Required Meaning
setRumAppId String Yes Set Rum AppId. Corresponds to setting RUM appid, which enables RUM collection, get appid method
setSamplingRate Float No Set collection rate, range [0,1], 0 means no collection, 1 means full collection, default is 1. Scope is all View, Action, LongTask, Error data under the same session_id
setSessionErrorSampleRate Float No Set error collection rate, when a session is not sampled by setSamplingRate, if an error occurs during the session, data from 1 minute before the error can be collected, range [0,1], 0 means no collection, 1 means full collection, default is 0. Scope is all View, Action, LongTask, Error data under the same session_id. Supported in ft-sdk 1.6.11 and above
setEnableTrackAppCrash Boolean No Whether to report App crash logs, default is false, enabling it will show error stack data in error analysis.
About obfuscation conversion in crash logs.

ft-sdk 1.5.1 and above, can use extraLogCatWithJavaCrash, extraLogCatWithNativeCrash to show logcat in Java Crash and Native Crash
setExtraMonitorTypeWithError Array No Set auxiliary monitoring information, add additional monitoring data to Rum crash data, ErrorMonitorType.BATTERY for battery level, ErrorMonitorType.MEMORY for memory usage, ErrorMonitorType.CPU for CPU usage, default is not set
setDeviceMetricsMonitorType Array No Set View monitoring information, add monitoring data during View lifecycle, DeviceMetricsMonitorType.BATTERY monitors the highest output current of the current page, DeviceMetricsMonitorType.MEMORY monitors the current application memory usage, DeviceMetricsMonitorType.CPU monitors CPU jumps, DeviceMetricsMonitorType.FPS monitors screen frame rate. Monitoring cycle, DetectFrequency.DEFAULT 500 ms, DetectFrequency.FREQUENT 100ms, DetectFrequency.RARE 1 second, default is not set
setEnableTrackAppANR Boolean No Whether to enable ANR detection, default is false.

ft-sdk 1.5.1 and above, can use extraLogCatWithANR to show logcat in ANR
setEnableTrackAppUIBlock Boolean, long No Whether to enable UI block detection, default is false, ft-sdk 1.6.4 and above can use blockDurationMs to control detection time range [100,), unit ms, default is 1 second
setEnableTraceUserAction Boolean No Whether to automatically trace user actions, currently only supports user start and click actions, default is false
setEnableTraceUserView Boolean No Whether to automatically trace user page actions, default is false
setEnableTraceUserViewInFragment Boolean No Whether to automatically trace Fragment type page data, default is false, supported in ft-sdk 1.6.11 and above
setEnableTraceUserResource Boolean No Whether to automatically trace user network requests, only supports Okhttp, default is false
setEnableResourceHostIP Boolean No Whether to collect the IP address of the request target domain. Scope: only affects the default collection when EnableTraceUserResource is true. Custom Resource collection needs to use FTResourceEventListener.FTFactory(true) to enable this function. Also, a single Okhttp has an IP cache mechanism for the same domain, the same OkhttpClient, if the server IP does not change, will only generate once
setResourceUrlHandler Callback No Set the Resource conditions to filter, default is no filter
setOkHttpEventListenerHandler Callback No ASM sets global Okhttp EventListener, default is not set
setOkHttpResourceContentHandler Callback No ASM sets global FTResourceInterceptor.ContentHandlerHelper, default is not set, supported in ft-sdk 1.6.7 and above, custom Resource
addGlobalContext Dictionary No Add custom tags for user monitoring data source differentiation, if tracing is needed, the parameter key is track_id, value is any value, add rule notes see here
setRumCacheLimitCount int No Local cache RUM limit count [10_000,), default is 100_000. Supported in ft-sdk 1.6.6 and above
setEnableTraceWebView Boolean No Whether to enable WebView data collection through Android SDK, default is true. Supported in ft-sdk 1.6.12 and above
setAllowWebViewHost Array No Set the WebView host addresses allowed for data tracing, null means all, default is null. Supported in ft-sdk 1.6.12 and above
setViewActivityTrackingHandler FTViewActivityTrackingHandler No Used to customize the tracking method of Activity views. When Activity lifecycle events occur, this handler is called to decide how to track the Activity, default is no processing. Supported in ft-sdk 1.6.13 and above
setViewFragmentTrackingHandler FTViewFragmentTrackingHandler No Used to customize the tracking method of Fragment views. When Fragment lifecycle events occur, this handler is called to decide how to track the Fragment. Supported in ft-sdk 1.6.13 and above
setActionTrackingHandler FTActionTrackingHandler No Used to customize the tracking method of user actions (app start, click). When user actions are performed, this handler is called to decide how to track the action, default is no processing. Supported in ft-sdk 1.6.13 and above

Log Configuration

FTSdk.initLogWithConfig(new FTLoggerConfig()
    //.setEnableConsoleLog(true,"log prefix")
    .setEnableLinkRumData(true)
    .setEnableCustomLog(true)
    //.setLogLevelFilters(new Status[]{Status.CRITICAL, Status.ERROR})
    .setSamplingRate(0.8f));
   FTSdk.initLogWithConfig(
            FTLoggerConfig()
              //.setEnableConsoleLog(true,"log prefix")
                .setEnableLinkRumData(true)
                .setEnableCustomLog(true)
              //.setLogLevelFilters(arrayOf(Status.CRITICAL,Status.ERROR))
                .setSamplingRate(0.8f)
        )
Method Name Type Required Meaning
setSamplingRate Float No Set collection rate, range [0,1], 0 means no collection, 1 means full collection, default is 1.
setEnableConsoleLog Boolean No Whether to report console logs, default false, log level correspondence
Log.v -> ok;
Log.i -> info;
Log.d -> debug;
Log.e -> error;
Log.w -> warning,
prefix is the console prefix filter parameter, default is no filter. Note: Android console logs are extensive, to avoid affecting application performance and unnecessary resource waste, it is recommended to use prefix to filter out valuable logs. ft-plugin 1.3.5 and above, supports capturing logs printed by Log.println
setEnableLinkRUMData Boolean No Whether to associate with RUM data, default is false
setEnableCustomLog Boolean No Whether to upload custom logs, default is false
setLogLevelFilters Array No Set log level filters, default is not set
addGlobalContext Dictionary No Add log global attributes, add rules see here
setLogCacheLimitCount Int No Local cache maximum log entry count limit [1000,), larger logs mean more disk cache pressure, default is 5000
setLogCacheDiscardStrategy LogCacheDiscard No Set log discard rules when the limit is reached, default is LogCacheDiscard.DISCARD, DISCARD discards additional data, DISCARD_OLDEST discards old data

Trace Configuration

FTSdk.initTraceWithConfig(new FTTraceConfig()
    .setSamplingRate(0.8f)
    .setEnableAutoTrace(true)
    .setEnableLinkRUMData(true));
   FTSdk.initTraceWithConfig(
            FTTraceConfig()
                .setSamplingRate(0.8f)
                .setEnableAutoTrace(true)
                .setEnableLinkRUMData(true)
        )
Method Name Type Required Meaning
setSamplingRate Float No Set collection rate, range [0,1], 0 means no collection, 1 means full collection, default is 1.
setTraceType TraceType No Set trace type, default is DDTrace, currently supports Zipkin , Jaeger, DDTrace, Skywalking (8.0+), TraceParent (W3C), if integrating OpenTelemetry, please check supported types and agent related configurations
setEnableLinkRUMData Boolean No Whether to associate with RUM data, default is false
setEnableAutoTrace Boolean No Whether to enable automatic http trace, currently only supports OKhttp automatic tracing, default is false
setOkHttpTraceHeaderHandler Callback No ASM sets global FTTraceInterceptor.HeaderHandler, default is not set, supported in ft-sdk 1.6.8 and above, example reference custom Trace

RUM User Data Tracing

Configure enableTraceUserAction, enableTraceUserView, enableTraceUserResource,setEnableTrackAppUIBlock,setEnableTrackAppCrash and setEnableTrackAppANR in FTRUMConfig to achieve automatic collection of Action, View, Resource, LongTask, Error data. For custom collection, you can use FTRUMGlobalManager to report data, as shown below:

Action

Usage

    /**
     * Add Action
     *
     * @param actionName action name
     * @param actionType action type
     * @param property   additional property parameters (optional)
     */
    public void startAction(String actionName, String actionType, HashMap<String, Object> property)

     /**
     * Add Action, this type of data cannot associate Error, Resource, LongTask data
     *
     * @param actionName action name
     * @param actionType action type
     * @param duration   nanoseconds, duration (optional)
     * @param property extension properties (optional)
     */
    public void addAction(String actionName, String actionType, long duration, HashMap<String, Object> property) 
    /**
     * Add action
     *
     * @param actionName action name
     * @param actionType action type
     * @param property   additional property parameters (optional)
     */
    fun startAction(actionName: String, actionType: String, property: HashMap<String, Any>)

    /**
     * Add Action
     *
     * @param actionName action name
     * @param actionType action type
     * @param duration   nanoseconds, duration (optional)
     * @param property extension properties (optional)
     */
    fun addAction(actionName: String, actionType: String, duration: Long, property: HashMap<String, Any>)

startAction internally has a duration calculation algorithm, during the calculation period it will try to associate with nearby Resource, LongTask, Error data, there is a 100 ms frequent trigger protection, recommended for user operation type data. If there is a need for frequent calls, use addAction, this data will not conflict with startAction and will not associate with current Resource, LongTask, Error data

Code Example

// Scenario 1
FTRUMGlobalManager.get().startAction("login", "action_type");

// Scenario 2: Dynamic parameters
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
FTRUMGlobalManager.get().startAction("login", "action_type", map);


// Scenario 1
FTRUMGlobalManager.get().addAction("login", "action_type");

// Scenario 2: Dynamic parameters
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
FTRUMGlobalManager.get().addAction("login", "action_type", map);
// Scenario 1
FTRUMGlobalManager.get().startAction("login", "action_type")

// Scenario 2: Dynamic parameters
val map = HashMap<String,Any>()
map["ft_key"]="ft_value"
FTRUMGlobalManager.get().startAction("login","action_type",map)


// Scenario 1
FTRUMGlobalManager.get().startAction("login", "action_type")

// Scenario 2: Dynamic parameters
val map = HashMap<String,Any>()
map["ft_key"]="ft_value"
FTRUMGlobalManager.get().startAction("login","action_type",map)

View

Usage

    /**
     * view start
     *
     * @param viewName current page name
     * @param property additional property parameters (optional)
     */
    public void startView(String viewName, HashMap<String, Object> property)

    /**
     * view end
     *
     * @param property additional property parameters (optional)
     */
    public void stopView(HashMap<String, Object> property)

    /**
     * Update current view loading_time metric, unit nanoseconds,
     * @param duration
     */
    public void updateLoadTime(long duration)
     /**
     * view start
     *
     * @param viewName current page name
     * @param property additional property parameters (optional)
     */

    fun startView(viewName: String, property: HashMap<String, Any>)

     /**
     * view end
     *
     * @param property additional property parameters (optional)
     */
    fun stopView(property: HashMap<String, Any>)

    /**
    * Update current view loading_time metric, unit nanoseconds
    * @param duration
    */
    fun updateLoadTime(duration: Long)

Code Example

@Override
protected void onResume() {
    super.onResume();

    // Scenario 1
    FTRUMGlobalManager.get().startView("Current Page Name");

    // Scenario 2: Dynamic parameters
    HashMap<String, Object> map = new HashMap<>();
    map.put("ft_key", "ft_value");
    map.put("ft_key_will_change", "ft_value");
    FTRUMGlobalManager.get().startView("Current Page Name", map);
}

@Override
protected void onPause() {
    super.onPause();

    // Scenario 1
    FTRUMGlobalManager.get().stopView();

    // Scenario 2 : Dynamic parameters
    HashMap<String, Object> map = new HashMap<>();
    map.put("ft_key_will_change", "ft_value_change"); //ft_key_will_change value will be modified to ft_value_change during stopView
    FTRUMGlobalManager.get().startView("Current Page Name", map);
}
override fun onResume() {
     super.onResume()

     // Scenario 1
     FTRUMGlobalManager.get().startView("Current Page Name")

     // Scenario 2: Dynamic parameters
     val map = HashMap<String, Any>()
     map["ft_key"] = "ft_value"
     map["ft_key_will_change"] = "ft_value"
     FTRUMGlobalManager.get().startView("Current Page Name", map)

}

override fun onPause() {
     super.onPause()

     // Scenario 1
     FTRUMGlobalManager.get().stopView()


     // Scenario 2 : Dynamic parameters
     val map = HashMap<String, Any>()
     map["ft_key_will_change"] = "ft_value_change" //ft_key_will_change value will be modified to ft_value_change during stopView
     FTRUMGlobalManager.get().startView("Current Page Name", map)

}

Error

Usage

    /**
     * Add error information
     *
     * @param log        log
     * @param message    message
     * @param errorType  error type, ErrorType
     * @param state      program running state
     * @param dateline   occurrence time, nanoseconds (optional)
     * @param property   additional properties (optional)
     */
    public void addError(String log, String message, long dateline, ErrorType errorType,
                         AppState state, HashMap<String, Object> property)
    /**
     * Add error information
     *
     * @param log        log
     * @param message    message
     * @param errorType  error type, String
     * @param state      program running state
     * @param dateline   occurrence time, nanoseconds (optional)
     * @param property   additional properties (optional)
     */
    public void addError(String log, String message, long dateline, String errorType,
                         AppState state, HashMap<String, Object> property)
     /**
     * Add error information
     *
     * @param log        log
     * @param message    message
     * @param errorType  error type, ErrorType
     * @param state      program running state
     * @param dateline   occurrence time, nanoseconds (optional)
     * @param property   additional properties (optional)
     */
    fun addError(log: String, message: String, dateline: Long, errorType: ErrorType,state: AppState, property: HashMap<String, Any>)

     /**
     * Add error information
     *
     * @param log        log
     * @param message    message
     * @param errorType  error type, String
     * @param state      program running state
     * @param dateline   occurrence time, nanoseconds (optional)
     * @param property   additional properties (optional)
     */
    fun addError(log: String, message: String, dateline: Long, errorType: String,state: AppState, property: HashMap<String, Any>)

Code Example

// Scenario 1:
FTRUMGlobalManager.get().addError("error log", "error msg", ErrorType.JAVA, AppState.RUN);

// Scenario 2: Delay recording the occurred error, the time here is usually the time the error occurred
FTRUMGlobalManager.get().addError("error log", "error msg", 16789000000000000000L, ErrorType.JAVA, AppState.RUN);

// Scenario 3:Dynamic parameters
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
FTRUMGlobalManager.get().addError("error log", "error msg", ErrorType.JAVA, AppState.RUN, map);
// Scenario 1:
FTRUMGlobalManager.get().addError("error log", "error msg", ErrorType.JAVA, AppState.RUN)

// Scenario 2: Delay recording the occurred error, the time here is usually the time the error occurred
FTRUMGlobalManager.get().addError("error log", "error msg", 16789000000000000000, ErrorType.JAVA, AppState.RUN)

// Scenario 3:Dynamic parameters
val map = HashMap<String, Any>()
map["ft_key"] = "ft_value"
FTRUMGlobalManager.get().addError("error log", "error msg",ErrorType.JAVA,AppState.RUN,map)

LongTask

Usage

    /**
     * Add long task
     *
     * @param log      log content
     * @param duration duration, nanoseconds
     * @param property   additional property parameters (optional)
     */
    public void addLongTask(String log, long duration, HashMap<String, Object> property)
    /**
     * Add long task
     *
     * @param log      log content
     * @param duration duration, nanoseconds
     * @param property   additional property parameters (optional)
     */

    fun addLongTask(log: String, duration: Long, property: HashMap<String, Any>)

Code Example

// Scenario 1
FTRUMGlobalManager.get().addLongTask("error log", 1000000L);

// Scenario 2: Dynamic parameters
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
FTRUMGlobalManager.get().addLongTask("", 1000000L, map);
// Scenario 1
FTRUMGlobalManager.get().addLongTask("error log",1000000L)

// Scenario 2: Dynamic parameters
 val map = HashMap<String, Any>()
 map["ft_key"] = "ft_value"
 FTRUMGlobalManager.get().addLongTask("", 1000000L,map)

Resource

Usage

    /**
     * resource start
     *
     * @param resourceId resource Id
     * @param property   additional property parameters (optional)
     */
    public void startResource(String resourceId, HashMap<String, Object> property)

    /**
     * resource end
     *
     * @param resourceId resource Id
     * @param property   additional property parameters (optional)
     */
    public void stopResource(final String resourceId, HashMap<String, Object> property)


    /**
     * Set network transmission content
     *
     * @param resourceId
     * @param params
     * @param netStatusBean
     */
    public void addResource(String resourceId, ResourceParams params, NetStatusBean netStatusBean)
/**
 * resource start
 *
 * @param resourceId resource Id (optional)
 */
fun startResource(resourceId: String, property: HashMap<String, Any>)


/**
 * resource end
 *
 * @param resourceId resource Id
 * @param property   additional property parameters (optional)
 */
fun stopResource(resourceId: String, property: HashMap<String, Any>)

/**
 * Set network transmission content
 *
 * @param resourceId
 * @param params
 * @param netStatusBean
 */
fun addResource(resourceId: String, params: ResourceParams, netStatusBean: NetStatusBean)

Code Example

// Scenario 1
// Request start
FTRUMGlobalManager.get().startResource("resourceId");

//...

// Request end
FTRUMGlobalManager.get().stopResource("resourceId");

// Finally, after the request ends, send the request related data metrics
ResourceParams params = new ResourceParams();
params.setUrl("https://guance.com");
params.setResponseContentType(response.header("Content-Type"));
params.setResponseConnection(response.header("Connection"));
params.setResponseContentEncoding(response.header("Content-Encoding"));
params.setResponseHeader(response.headers().toString());
params.setRequestHeader(request.headers().toString());
params.setResourceStatus(response.code());
params.setResourceMethod(request.method());

NetStatusBean bean = new NetStatusBean();
bean.setTcpStartTime(60000000);
//...

FTRUMGlobalManager.get().addResource("resourceId", params, bean);


// Scenario 2 :Dynamic parameters usage
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
map.put("ft_key_will_change", "ft_value");

FTRUMGlobalManager.get().startResource("resourceId",map);

//...
HashMap<String, Object> map = new HashMap<>()
map.put("ft_key_will_change", "ft_value_change"); //ft_key_will_change value will be modified to ft_value_change during stopResource
FTRUMGlobalManager.get().stopResource(uuid,map);
// Scenario 1
//Request start
FTRUMGlobalManager.get().startResource("resourceId")

//Request end
FTRUMGlobalManager.get().stopResource("resourceId")

//Finally, after the request ends, send the request related data metrics
val params = ResourceParams()
params.url = "https://guance.com"
params.responseContentType = response.header("Content-Type")
arams.responseConnection = response.header("Connection")
params.responseContentEncoding = response.header("Content-Encoding")
params.responseHeader = response.headers.toString()
params.requestHeader = request.headers.toString()
params.resourceStatus = response.code
params.resourceMethod = request.method

val bean = NetStatusBean()
bean.tcpStartTime = 60000000
//...
FTRUMGlobalManager.get().addResource("resourceId",params,bean)

// Scenario 2 :Dynamic parameters usage
val map = hashMapOf<String, Any>(
        "ft_key" to "ft_value",
        "ft_key_will_change" to "ft_value"
)
FTRUMGlobalManager.get().startResource("resourceId", map)

//...
val map = hashMapOf<String, Any>(
        "ft_key_will_change" to "ft_value_change"
)
// ft_key_will_change value will be modified to ft_value_change during stopResource

FTRUMGlobalManager.get().stopResource(uuid, map)
Method Name Required Meaning Note
NetStatusBean.fetchStartTime No Request start time
NetStatusBean.tcpStartTime No tcp connection time
NetStatusBean.tcpEndTime No tcp end time
NetStatusBean.dnsStartTime No dns start time
NetStatusBean.dnsEndTime No dns end time
NetStatusBean.responseStartTime No Response start time
NetStatusBean.responseEndTime No Response end time
NetStatusBean.sslStartTime No ssl start time
NetStatusBean.sslEndTime No ssl end time
NetStatusBean.property No Additional properties
ResourceParams.url Yes url address
ResourceParams.requestHeader No Request header parameters
ResourceParams.responseHeader No Response header parameters
ResourceParams.responseConnection No Response connection
ResourceParams.responseContentType No Response ContentType
ResourceParams.responseContentEncoding No Response ContentEncoding
ResourceParams.resourceMethod No Request method GET,POST etc.
ResourceParams.responseBody No Return body content
ResourceParams.property No Additional properties

Logger Log Printing

Use FTLogger for custom log output, requires enabling FTLoggerConfig.setEnableCustomLog(true).

Currently, log content is limited to 30 KB, characters beyond this limit will be truncated

Usage

    /**
     * Store a single log data locally and sync
     *
     * @param content log content
     * @param status  log level, enum Status
     * @param property additional properties (optional)
     */
    public void logBackground(String content, Status status, HashMap<String, Object> property)

    /**
     * Store a single log data locally and sync
     *
     * @param content log content
     * @param status  log level, String
     * @param property additional properties (optional)
     */
    public void logBackground(String content, String status, HashMap<String, Object> property)

    /**
     * Store multiple log data locally and sync
     *
     * @param logDataList {@link LogData} list
     */
    public void logBackground(List<LogData> logDataList)
    /**
     * Store a single log data locally and sync
     *
     * @param content log content
     * @param status  log level
     * @param property log properties (optional)
     */
    fun logBackground(content: String, status: Status, property: HashMap<String, Any>)


    /**
     * Store a single log data locally and sync
     *
     * @param content log content
     * @param status  log level
     * @param property log properties (optional)
     */
    fun logBackground(content: String, status: String, property: HashMap<String, Any>)

    /**
     * Store multiple log data locally and sync
     *
     * @param logDataList log data list
     */
    fun logBackground(logDataList: List<LogData>)

Log Levels

Method Name Meaning
Status.DEBUG Debug
Status.INFO Info
Status.WARNING Warning
Status.ERROR Error
Status.CRITICAL Critical
Status.OK OK

Code Example

// Upload single log
FTLogger.getInstance().logBackground("test", Status.INFO);

// Pass parameters to HashMap
HashMap<String, Object> map = new HashMap<>();
map.put("ft_key", "ft_value");
FTLogger.getInstance().logBackground("test", Status.INFO, map);

// Batch upload logs
List<LogData> logList = new ArrayList<>();
logList.add(new LogData("test", Status.INFO));
FTLogger.getInstance().logBackground(logList);
//Upload single log
FTLogger.getInstance().logBackground("test", Status.INFO)

//Pass parameters to HashMap
val map = HashMap<String,Any>()
map["ft_key"]="ft_value"
FTLogger.getInstance().logBackground("test", Status.INFO,map)

//Batch upload logs
FTLogger.getInstance().logBackground(mutableListOf(LogData("test",Status.INFO)))

Tracer Network Trace

FTTraceConfig configuration enables enableAutoTrace to automatically add trace data, or manually use FTTraceManager to add Propagation Header in Http requests, as shown below:

String url = "https://request.url";
String uuid = "uuid";
// Get trace header parameters
Map<String, String> headers = FTTraceManager.get().getTraceHeader(uuid, url);

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(chain -> {
    Request original = chain.request();
    Request.Builder requestBuilder = original.newBuilder();
    // Add trace header parameters in the request
    for (String key : headers.keySet()) {
        requestBuilder.header(key, headers.get(key));
    }
    Request request = requestBuilder.build();

    Response response = chain.proceed(request);

    if (response != null) {
        Map<String, String> requestHeaderMap = new HashMap<>();
        Map<String, String> responseHeaderMap = new HashMap<>();
        for (Pair<String, String> header : response.request().headers()) {
            requestHeaderMap.put(header.first, header.second);
        }
        for (Pair<String, String> header : response.headers()) {
            responseHeaderMap.put(header.first, header.second);
    }}

    return response;
}).build();

Request.Builder builder = new Request.Builder().url(url).method(RequestMethod.GET.name(), null);
client.newCall(builder.build()).execute();
val url = "https://request.url"
val uuid ="uuid"
//Get trace header parameters
val headers = FTTraceManager.get().getTraceHeader(uuid, url)

val client: OkHttpClient = OkHttpClient.Builder().addInterceptor { chain ->

                    val original = chain.request()
                    val requestBuilder = original.newBuilder()
                    //Add trace header parameters in the request
                    for (key in headers.keys) {
                        requestBuilder.header(key!!, headers[key]!!)
                    }
                    val request = requestBuilder.build()

                    response = chain.proceed(request)

                    if (response != null) {
                        val requestHeaderMap = HashMap<String, String>()
                        val responseHeaderMap = HashMap<String, String>()
                        request.headers.forEach {
                            requestHeaderMap[it.first] = it.second
                        }
                        response!!.headers.forEach {
                            responseHeaderMap[it.first] = it.second

                        }

                    }

                    response!!
                }.build()

 val builder: Request.Builder = Request.Builder().url(url).method(RequestMethod.GET.name, null)
client.newCall(builder.build()).execute()

Custom Resource and TraceHeader via OKHttp Interceptor

FTRUMConfig's enableTraceUserResourceFTTraceConfig's enableAutoTrace configuration, when both are enabled, custom Interceptor configuration is loaded first,

ft-sdk < 1.4.1, need to disable FTRUMConfig's enableTraceUserResourceFTTraceConfig's enableAutoTrace. ft-sdk > 1.6.7 supports custom Trace Header association with RUM data

 new OkHttpClient.Builder()
        .addInterceptor(new FTTraceInterceptor(new FTTraceInterceptor.HeaderHandler() {
               @Override
               public HashMap<String, String> getTraceHeader(Request request) {
                   HashMap<String, String> map = new HashMap<>();
                   map.put("custom_header","custom_value");
                   return map;
              }

             // 1.6.7 and above support
              @Override
              public String getSpanID() {
                return "span_id";
             }
            // 1.6.7 and above support
             @Override
             public String getTraceID() {
                return "trace_id";
             }
        }))
       .addInterceptor(new FTResourceInterceptor(new FTResourceInterceptor.ContentHandlerHelper() {
           @Override
           public void onRequest(Request request, HashMap<String, Object> extraData) {
               String contentType = request.header("Content-Type");
               extraData.put("df_request_header", request.headers().toString());
               if ("application/json".equals(contentType) ||
                       "application/x-www-form-urlencoded".equals(contentType) ||
                       "application/xml".equals(contentType)) {
                   extraData.put("df_request_body", request.body());


           @Override
           public void onResponse(Response response, HashMap<String, Object> extraData) throws IOException {
               String contentType = response.header("Content-Type");
               extraData.put("df_response_header", response.headers().toString());
               if ("application/json".equals(contentType) ||
                       "application/xml".equals(contentType)) {
                   //copy part of the body to avoid large data consumption
                   ResponseBody body = response.peekBody(33554432);
                   extraData.put("df_response_body", body.string());
               }

           @Override
           public void onException(Exception e, HashMap<String, Object> extraData)
           }
       }))
       .eventListenerFactory(new FTResourceEventListener.FTFactory())
       .build();
OkHttpClient.Builder()
.addInterceptor(FTTraceInterceptor(object : FTTraceInterceptor.HeaderHandler {
    override fun getTraceHeader(request: Request): HashMap<String, String> {
        val map = HashMap<String, String>()
        map["custom_header"] = "custom_value"
        return map
    }
}))
.addInterceptor(FTResourceInterceptor(object : FTResourceInterceptor.ContentHandlerHelper {
    override fun onRequest(request: Request, extraData: HashMap<String, Any>) {
        val contentType = request.header("Content-Type")
        extraData["df_request_header"] = request.headers().toString()
        if ("application/json" == contentType ||
            "application/x-www-form-urlencoded" == contentType ||
            "application/xml" == contentType) {
            extraData["df_request_body"] = request.body()
        }
    }

    override fun onResponse(response: Response, extraData: HashMap<String, Any>) {
        val contentType = response.header("Content-Type")
        extraData["df_response_header"] = response.headers().toString()
        if ("application/json" == contentType ||
            "application/xml" == contentType) {
            // Copy part of the response body to avoid large data consumption
            val body = response.peekBody(33554432)
            extraData["df_response_body"] = body.string()
        }
    }

    override fun onException(e: Exception, extraData: HashMap<String, Any>) {
        // Handle exception cases
    }
}))
.eventListenerFactory(FTResourceEventListener.FTFactory())
.build()

ContentHandlerHelperEx Local Network Error Filtering

ContentHandlerHelperEx is an enhanced version of ContentHandlerHelper, which can filter local network IOException errors.

new FTResourceInterceptor.ContentHandlerHelperEx() {
    //...

    /**
     * Return exceptions during network connection
     *
     * @param e         IOException data occurred during the request
     * @param extraData additional data
     * @return Whether to filter local network network_error type errors. true, to override
     */
    @Override
    public boolean onExceptionWithFilter(Exception e, HashMap<String, Object> extraData) {
        if (e instanceof SocketTimeoutException) { //Network timeout
            return true;
        }
        return super.onExceptionWithFilter(e, extraData);
    }
}
object : FTResourceInterceptor.ContentHandlerHelperEx() {
    //...

    /**
    * Return exceptions during network connection
    *
    * @param e         IOException data occurred during the request
    * @param extraData additional data
    * @return Whether to filter local network network_error type errors. true, to override
    */
    override fun onExceptionWithFilter(e: Exception, extraData: HashMap<String, Any>): Boolean {
        return if (e is SocketTimeoutException) {
            true
        } else {
            super.onExceptionWithFilter(e, extraData)
        }
    }
}

Add ResourceID to OKhttp Request

Add uuid to Okhttp Request, recommended to enable for high concurrency scenarios with the same request. ft-plugin 1.3.5 and above, ft-sdk 1.6.10 and above, enable FTSDKConfig.setEnableOkhttpRequestTag(true) to automatically add ResourceID to Request

new Request.Builder()
//...
.tag(ResourceID.class, new ResourceID())//Add unique
Request.Builder()
// ...
.tag(ResourceID::class.java, ResourceID()) 

User Information Binding and Unbinding

Use FTSdk to bind and unbind user information

Usage

   /**
     * Bind user information
     *
     * @param id
     */
    public static void bindRumUserData(@NonNull String id)

    /**
     * Bind user information
     */
    public static void bindRumUserData(@NonNull UserData data)

    /**
     * Unbind user information
     */
    public static void unbindRumUserData()
/**
     * Bind user information
     *
     * @param id User ID
     */
    fun bindRumUserData(id: String)

    /**
     * Bind user information
     *
     * @param data User information
     */
    fun bindRumUserData(data: UserData)

    /**
     * Unbind user information
     */
    fun unbindRumUserData()

UserData

Method Name Meaning Required Note
setId Set user ID No
setName Set username No
setEmail Set email No
setExts Set user extensions No Add rules see here

Code Example

// Can call this method after user login to bind user information
FTSdk.bindRumUserData("001");

UserData userData = new UserData();
userData.setName("test.user");
userData.setId("test.id");
userData.setEmail("test@mail.com");
Map<String, String> extMap = new HashMap<>();
extMap.put("ft_key", "ft_value");
userData.setExts(extMap);
FTSdk.bindRumUserData(userData);

// Can call this method after user logout to unbind user information
FTSdk.unbindRumUserData();
//Can call this method after user login to bind user information
FTSdk.bindRumUserData("001")

//Bind more user data
val userData = UserData()
userData.name = "test.user"
userData.id = "test.id"
userData("test@mail.com")
val extMap = HashMap<String, String>()
extMap["ft_key"] = "ft_value"
userData.setExts(extMap)
FTSdk.bindRumUserData(userData)

//Can call this method after user logout to unbind user information
FTSdk.unbindRumUserData()

Shut Down SDK

Use FTSdk to shut down the SDK, if dynamically changing SDK configuration, need to shut down first to avoid incorrect data generation

FTSdk.shutDown();
FTSdk.shutDown()

Clear SDK Cache Data

Use FTSdk to clear unreported cache data

FTSdk.clearAllData();
FTSdk.clearAllData()

Actively Sync Data

Use FTSdk to actively sync data.

FTSdk.setAutoSync(false) is required, then you need to manage data sync manually

FTSdk.flushSyncData()
FTSdk.flushSyncData()

Actively Sync Dynamic Configuration

Use FTSdk to actively sync dynamic configuration. When automatic updates do not meet requirements, adjust the update timing by actively calling the method.

/**
 *  Actively update remote configuration, call frequency is affected by FTSDKConfig.setRemoteConfigMiniUpdateInterval
 */
FTSdk.updateRemoteConfig();
/**
 * Actively update remote configuration, this method ignores FTSDKConfig.setRemoteConfigMiniUpdateInterva configuration
 *
 * @param remoteConfigMiniUpdateInterval remote configuration interval, unit seconds [0,)
 * @param result                         returns update result
 */
FTSdk.updateRemoteConfig(int remoteConfigMiniUpdateInterval, FTRemoteConfigManager.FetchResult result);
/**
 *  Actively update remote configuration, call frequency is affected by FTSDKConfig.setRemoteConfigMiniUpdateInterval
 */
FTSdk.updateRemoteConfig()
/**
 * Actively update remote configuration, this method ignores FTSDKConfig.setRemoteConfigMiniUpdateInterva configuration
 *
 * @param remoteConfigMiniUpdateInterval remote configuration interval, unit seconds [0,)
 * @param result                         returns update result
 */
FTSdk.updateRemoteConfig(remoteConfigMiniUpdateInterval:Int,result:FTRemoteConfigManager.FetchResult)

Dynamically Enable and Disable AndroidID Access

Use FTSdk to set whether to access Android ID in the SDK

// Enable Android ID access
FTSdk.setEnableAccessAndroidID(true);

// Disable Android ID access
FTSdk.setEnableAccessAndroidID(false);
//Enable Android ID access
FTSdk.setEnableAccessAndroidID(true)

//Disable Android ID access
FTSdk.setEnableAccessAndroidID(false)

Add Custom Tags

Use FTSdk to dynamically add tags during SDK runtime

Usage

/**
 * Dynamically set global tag
 * @param globalContext
 */
public static void appendGlobalContext(HashMap<String,Object> globalContext)

/**
 * Dynamically set RUM global tag
 * @param globalContext
 */
public static void appendRUMGlobalContext(HashMap<String,Object> globalContext)

/**
 * Dynamically set log global tag
 * @param globalContext
 */
public static void appendLogGlobalContext(HashMap<String,Object> globalContext)
/**
 * Dynamically set global tag
 * @param globalContext
 */
fun appendGlobalContext(globalContext: HashMap<String, Any>) 

/**
 * Dynamically set RUM global tag
 * @param globalContext
 */
fun appendRUMGlobalContext(globalContext: HashMap<String, Any>) 

/**
 * Dynamically set log global tag
 * @param globalContext
 */
fun appendLogGlobalContext(globalContext: HashMap<String, Any>)

Code Example

HashMap<String, Object> globalContext = new HashMap<>();
globalContext.put("global_key", "global_value");
FTSdk.appendGlobalContext(globalContext);

HashMap<String, Object> rumGlobalContext = new HashMap<>();
rumGlobalContext.put("rum_key", "rum_value");
FTSdk.appendRUMGlobalContext(rumGlobalContext);

HashMap<String, Object> logGlobalContext = new HashMap<>();
logGlobalContext.put("log_key", "log_value");
FTSdk.appendLogGlobalContext(logGlobalContext);
val globalContext = hashMapOf<String, Any>(
    "global_key" to "global_value"
)
FTSdk.appendGlobalContext(globalContext)

val rumGlobalContext = hashMapOf<String, Any>(
    "rum_key" to "rum_value"
)
FTSdk.appendRUMGlobalContext(rumGlobalContext)

val logGlobalContext = hashMapOf<String, Any>(
    "log_key" to "log_value"
)
FTSdk.appendLogGlobalContext(logGlobalContext)

Symbol File Upload

Plugin Upload (Only Supports Datakit [Local Deployment])

ft-plugin version 1.3.0 and above supports the latest symbol file upload rules, supports productFlavor multi-version management, plugin will execute symbol file upload after gradle task assembleRelease, detailed configuration can refer to SDK Demo

FTExt {
    //...
    autoUploadMap = true                 // Upload mapping.txt file, default is false
    autoUploadNativeDebugSymbol = true   // Upload c/c++ symbol so file, default is false
    datakitUrl = 'https://datakit.url'  // Datakit upload address, generateSourceMapOnly=true is not required
    datawayToken = 'dataway_token'      // Workspace token, generateSourceMapOnly=true is not required
    appId = "appid_xxxxx"               // appid, generateSourceMapOnly=true is not required
    env = 'common'                      // Environment, generateSourceMapOnly=true is not required
    // native so specified path, just specify the upper directory of the abi file
    // |-stripped_native_libs
    //      |-release
    //          |-out
    //          |-lib
    //              |-arm64-v8a
    //              |-armeabi-v7a
    //              |-...
    //nativeLibPath='/build/intermediates/merged_native_libs/release/out/lib'
    generateSourceMapOnly = false //Only generate sourcemap, default is false, path example: /app/build/tmp/ft{flavor}SourceMapMerge-release.zip, ft-plugin:1.3.4 and above support

    prodFlavors { //prodFlavors configuration will override outer settings
        prodTest {
            autoUploadMap = false
            autoUploadNativeDebugSymbol = false
            datakitUrl = 'https://datakit.url'
            datawayToken = 'dataway_token'
            appId = "appid_prodTest"
            env = "gray"
        }
        prodPublish {
            autoUploadMap = true
            autoUploadNativeDebugSymbol = true
            datakitUrl = 'https://datakit.url'
            datawayToken = 'dataway_token'
            appId = "appid_prodPublish"
            env = "prod"
        }
    }
}

Manual Upload

Use plugin to enable generateSourceMapOnly = true, execute gradle task assembleRelease to generate, or manually package into zip file, then manually upload to datakit or upload from Guance Studio, it is recommended to use zip command line to package, to avoid packaging system hidden files into the zip package, symbol upload refer to sourcemap upload

Unity Native Symbol files refer to official documentation

Permission Configuration Description

Name Required Usage Reason
READ_PHONE_STATE No Used to obtain the device information of the cellular network

For how to apply for dynamic permissions, refer to Android Developer

Plugin AOP Ignore

Add @IngoreAOP to the method covered by Plugin AOP to ignore ASM insertion. If batch ignore is needed, use ft-plugin FTExt ignorePackages to ignore.

View.setOnClickListener(new View.OnClickListener() {
        @Override
        @IgnoreAOP
        public void onClick(View v) {

        }
    }
View.setOnClickListener @IngoreAOP{

    }

WebView Data Monitoring

WebView data monitoring requires integrating Web Monitoring SDK in the WebView access page.

Data Masking

If you want to fully mask a field, it is recommended to use setDataModifier, which performs better. If you need detailed rule replacement, it is recommended to use setLineDataModifier

Do not use complex or high-latency methods in the callback method, this will greatly affect SDK data writing performance

FTSdk.install(
    FTSDKConfig.builder("xxx")
    .setDataModifier(new DataModifier() {
    /**
     * Modify a single field
     * 
     * @param key    field name
     * @param value  field value (original value)
     * @return new value, return null to not change
     */
    @Override
    public Object modify(String key, Object value) {
        if (key.equals("device_uuid")) {
            return "xxx";
        }
        return null;
    }
    }).setLineDataModifier(new LineDataModifier() {
    /***
     * Modify a single line of data
     * 
     * @param measurement data metric type view, action, resource,
     *                    longtask, error, df_rum_android_log
     * @param data original data key-value pairs
     * @return key-value pairs to modify, (return null or empty map to not change)
     */
    @Override
    public Map<String, Object> modify(String measurement, HashMap<String, Object> data) {
        if(measurement.equals("view")){
            HashMap<String,Object> changeMap = new HashMap<String,Object>();
            changeMap.put("view_url", "xxx");
        }
        return null;
    }
}))     
FTSdk.install(
FTSDKConfig.builder("xxx")
.setDataModifier(object : DataModifier {
    /**
     * Modify a single field
     * 
     * @param key    field name
     * @param value  field value (original value)
     * @return new value, return null to not change
     */
    override fun modify(key: String, value: Any?): Any? {
        return if (key == "device_uuid") {
            "xxx" // Replace with custom device_uuid
        } else {
            null
        }
    }
})
// Batch modify certain fields in a single line of data
.setLineDataModifier(object : LineDataModifier {
    /**
     * Modify a single line of data
     * 
     * @param measurement data metric type view, action, resource,
     *        longtask, error, df_rum_android_log
     * @param data original data key-value pairs
     * @return key-value pairs to modify, (return null or empty map to not change)
     */
    override fun modify(
        measurement: String,
        data: HashMap<String, Any>
    ): Map<String, Any>? {
        return if (measurement == "view") {
            hashMapOf("view_url" to "xxx") 
        } else {
            null
        }
    }
}))

RUM Custom Collection Rules

View

Activity

  • Requires FTRUMConfig.setEnableTraceUserView(true) to be enabled
FTSdk.initRUMWithConfig(new FTRUMConfig()
    .setViewActivityTrackingHandler(new FTViewActivityTrackingHandler() {
        @Override
        public HandlerView resolveHandlerView(Activity activity) {
            String activityName = activity.getClass().getSimpleName();

            // Customize view name based on Activity name
            if (activityName.startsWith("Main")) {
                return new HandlerView("Custom Main Page");
            } else if (activityName.startsWith("Detail")) {
                HashMap<String, Object> properties = new HashMap<>();
                properties.put("extra_key", "extra_value");
                return new HandlerView("Custom Detail Page", properties);
            }

            // Return null to skip tracking
            return null;
        }
    })
);
FTSdk.initRUMWithConfig(
    FTRUMConfig()
        .setViewActivityTrackingHandler(object : FTViewActivityTrackingHandler {
            override fun resolveHandlerView(activity: Activity): HandlerView? {
                val activityName = activity.javaClass.simpleName
                return when {
                    activityName.st.startsWith("Main") ->
                        HandlerView("Custom Main Page")
                    activityName.startsWith("Detail") -> {
                        val properties = hashMapOf<String, Any>("extra_key" to "extra_value")
                        HandlerView("Custom Detail Page", properties)
                    }
                    else -> null // Skip tracking
                }
            }
        })
)

Fragment

  • Requires both FTRUMConfig.setEnableTraceUserView(true) and FTRUMConfig.setEnableTraceUserViewInFragment(true) to be enabled
FTSdk.initRUMWithConfig(new FTRUMConfig()
    .setViewFragmentTrackingHandler(new FTViewFragmentTrackingHandler() {
        @Override
        public HandlerView resolveHandlerView(FragmentWrapper fragment) {
            String fragmentName = fragment.getSimpleClassName();

            // Customize view name based on Fragment name
            if (fragmentName.equals("HomeFragment")) {
                return new HandlerView("Custom Home Fragment");
            } else if (fragmentName.startsWith("Detail")) {
                HashMap<String, Object> properties = new HashMap<>();
                properties.put("extra_key", "extra_value");
                return new HandlerView("Custom Detail Fragment", properties);
            }

            // Return null to skip tracking
            return null;
        }
    })
);
FTSdk.initRUMWithConfig(
    FTRUMConfig()
        .setViewFragmentTrackingHandler(object : FTViewFragmentTrackingHandler {
            override fun resolveHandlerView(fragment: FragmentWrapper): HandlerView? {
                val fragmentName = fragment.simpleClassName
                return when {
                    fragmentName == "HomeFragment" ->
                        HandlerView("Custom Home Fragment")
                    fragmentName.startsWith("Detail") -> {
                        val properties = hashMapOf<String, Any>("extra_key" to "extra_value")
                        HandlerView("Custom Detail Fragment", properties)
                    }
                    else -> null // Skip tracking
                }
            }
        })
)

Action

  • Requires FTRUMConfig.setEnableTraceUserAction(true) to be enabled
FTSdk.initRUMWithConfig(new FTRUMConfig()
    .setActionTrackingHandler(new FTActionTrackingHandler() {
        @Override
        public HandlerAction resolveHandlerAction(ActionEventWrapper actionEventWrapper) {
            // Get action type
            ActionSourceType actionType = actionEventWrapper.getSourceType();

            // Customize tracking based on action type
            if (actionType == ActionSourceType.CLICK_VIEW) {
                HashMap<String, Object> properties = new HashMap<>();
                properties.put("extra_key", "extra_value");
                return new HandlerAction("Custom Button Click", properties);
            } else if (actionType == ActionSourceType.CLICK_LIST_ITEM) {
                return new HandlerAction("Custom List Item Click");
            }

            // Return null to skip tracking
            return null;
        }
    })
);
FTSdk.initRUMWithConfig(
    FTRUMConfig()
        .setActionTrackingHandler(object : FTActionTrackingHandler {
            override fun resolveHandlerAction(actionEventWrapper: ActionEventWrapper): HandlerAction? {
                return when (actionEventWrapper.sourceType) {
                    ActionSourceType.CLICK_VIEW -> {
                        val properties = hashMapOf<String, Any>("extra_key" to "extra_value")
                        HandlerAction("Custom Button Click", properties)
                    }
                    ActionSourceType.CLICK_LIST_ITEM ->
                        HandlerAction("Custom List Item Click")
                    else -> null // Skip tracking
                }
            }
        })
)

Resource

  • Requires FTRUMConfig.setEnableTraceUserResource(true) to be enabled
FTSdk.initRUMWithConfig(new FTRUMConfig()
    .setResourceUrlHandler(new FTInTakeUrlHandler() {
        @Override
        public boolean isInTakeUrl(String url) {
            // Return true to not collect; return false to collect
            return url.startsWith("https://url.rule");
        }
    })
);
FTSdk.initRUMWithConfig(
    FTRUMConfig()
        .setResourceUrlHandler(object : FTInTakeUrlHandler {
            override fun isInTakeUrl(url: String): Boolean {
                // Return true to not collect; return false to collect
                return url.startsWith("https://url.rule")
            }
        })
)

Custom Tag Usage Example

Compile Configuration

  1. Create multiple productFlavors in build.gradle to differentiate tags
android{
    //…
    productFlavors {
        prodTest {
            buildConfigField "String", "CUSTOM_VALUE", "\"Custom Test Value\""
            //…
        }
        prodPublish {
            buildConfigField "String", "CUSTOM_VALUE", "\"Custom Publish Value\""
            //…
        }
    }
}
  1. Add corresponding BuildConfig constants in RUM configuration
FTSdk.initRUMWithConfig(
        new FTRUMConfig()
            .addGlobalContext(CUSTOM_STATIC_TAG, BuildConfig.CUSTOM_VALUE)
            //... Add other configurations
);
FTSdk.initRUMWithConfig(
            FTRUMConfig()
                .addGlobalContext(CUSTOM_STATIC_TAG, BuildConfig.CUSTOM_VALUE)
                //… Add other configurations
        )

Runtime File Read/Write

  1. Store data in file types, such as SharedPreferences, configure SDK, add code to retrieve tag data in the configuration.
SharedPreferences sp = context.getSharedPreferences(SP_STORE_DATA, MODE_PRIVATE);
String customDynamicValue = sp.getString(CUSTOM_DYNAMIC_TAG, "not set");

// Configure RUM
FTSdk.initRUMWithConfig(
     new FTRUMConfig().addGlobalContext(CUSTOM_DYNAMIC_TAG, customDynamicValue)
     //… Add other configurations
);
val sp = context.getSharedPreferences(SP_STORE_DATA, MODE_PRIVATE)
val customDynamicValue = sp.getString(CUSTOM_DYNAMIC_TAG, "not set")

//Configure RUM
FTSdk.initRUMWithConfig(
     FTRUMConfig().addGlobalContext(CUSTOM_DYNAMIC_TAG, customDynamicValue!!)
     //… Add other configurations
)
  1. Add methods to change file data anywhere.
public void setDynamicParams(Context context, String value) {
    SharedPreferences sp = context.getSharedPreferences(SP_STORE_DATA, MODE_PRIVATE);
    sp.edit().putString(CUSTOM_DYNAMIC_TAG, value).apply();
}
fun setDynamicParams(context: Context, value: String) {
            val sp = context.getSharedPreferences(SP_STORE_DATA, MODE_PRIVATE)
            sp.edit().putString(CUSTOM_DYNAMIC_TAG, value).apply()

        }
  1. Finally, restart the application, detailed details can be found in SDK Demo

Add Tags During SDK Runtime

After SDK initialization, use FTSdk.appendGlobalContext(globalContext), FTSdk.appendRUMGlobalContext(globalContext), FTSdk.appendLogGlobalContext(globalContext) to dynamically add tags. After setting, it will take effect immediately. Subsequently, RUM or Log data will automatically include the tag data. This usage is suitable for scenarios where data is obtained with a delay, such as tag data obtained through network requests.

//SDK initialization pseudo code, obtain parameters from the network, then set tags

FTSdk.init() 

getInfoFromNet(info){
    HashMap<String, Object> globalContext = new HashMap<>();
    globalContext.put("delay_key", info.value);
    FTSdk.appendGlobalContext(globalContext)
}

Content Provider Setup Guide

To optimize multi-process data collection. ft-sdk: >= 1.6.14 version uses ContentProvider. SDK uses the following configuration by default, and will adapt according to different applicationIds.

<provider
    android:name="com.ft.garble.db.FTContentProvider"
    android:authorities="${applicationId}.com.ft.sdk.provider"
    android:exported="false"
    android:multiprocess="true" >
</provider>

Custom Provider

If customization is needed, then tools:replace is required to override the provider settings, set meta-data

Note that android:authorities in provider needs to match android:value in meta-data

<provider
    tools:replace="android:authorities"
    android:name="com.ft.sdk.garble.db.FTContentProvider"
    android:authorities="com.custom.app.provider" 
    android:exported="false"
    android:multiprocess="true" >
</provider> 

<meta-data
    android:name="com.ft.sdk.PROVIDER_AUTHORITY"
    android:value="com.custom.app.provider" /> 

Frequently Asked Questions

Add Global Variables to Avoid Conflict Fields

To avoid conflicts between custom fields and SDK data, it is recommended to add a project abbreviation prefix to tag names, such as df_tag_name. The key values used in the project can be found in the source code. When the same variable appears in the SDK global variables and RUM, Log, RUM, Log will override the global variables in the SDK.

SDK Compatibility

Handling Market Privacy Audits

Privacy Statement

View Here

Method 1: SDK AndroidID Configuration

SDK uses Android ID to better associate the same user data. If the application needs to be listed on the market, the following method is needed to handle market privacy audits.

public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        // Set setEnableAccessAndroidID to false during initialization
        FTSDKConfig config = new FTSDKConfig.Builder(DATAKIT_URL)
                .setEnableAccessAndroidID(false)
                .build();
        FTSdk.install(config);

        // ...
    }
}

// Enable after user agrees to privacy policy
FTSdk.setEnableAccessAndroidID(true);
class DemoApplication : Application() {
    override fun onCreate() {

        //Set setEnableAccessAndroidID to false during initialization
        val config = FTSDKConfig
            .builder(DATAKIT_URL)
            . setEnableAccessAndroidID(false)

        FTSdk.install(config)

        //...
    }
}

//Enable after user agrees to privacy policy
FTSdk.setEnableAccessAndroidID(true);

Method 2: Delay SDK Initialization

If the SDK needs to be loaded with a delay in the application, it is recommended to initialize it in the following way.

// Application
public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        //If the agreement has been agreed, initialize in Application
        if(agreeProtocol){
            FTSdk.init(); //SDK initialization pseudo code
        }
    }
}

// Privacy Statement Activity Page
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //Privacy statement not read
        if ( notReadProtocol ) {
            //Show privacy statement popup
            showProtocolView();

            //If the privacy statement is agreed
            if( agreeProtocol ){
                FTSdk.init(); //SDK initialization pseudo code
            }
        }
    }
}
// Application  
class DemoApplication : Application() {
    override fun onCreate() {
        // If the agreement has been agreed, initialize in Application
        if (agreeProtocol) {
            FTSdk.init() //SDK initialization pseudo code
        }
    }
}

// Privacy Statement Activity Page
class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Privacy statement not read
        if (notReadProtocol) {
            // Show privacy statement popup
            showProtocolView()

            // If the privacy statement is agreed
            if (agreeProtocol) {
                FTSdk.init() //SDK initialization pseudo code
            }
        }
    }
}

Third-party Frameworks

flutter, react-native, uni-app, unity can use a similar delayed initialization method as native Android to handle market privacy audits.

Jetpack Compose Support

Currently, automatic collection of compose component generated pages is not supported, but manual Action and View custom interfaces can be used to track click events and page navigation events, refer to here

How to Integrate SDK Without Using ft-plugin

Guance uses Android Gradle Plugin Transformation to implement code injection, thereby achieving automatic data collection. However, due to some compatibility issues, there may be situations where ft-plugin or ft-plugin-legacy cannot be used. Affected features include RUM Action, Resource, and android.util.Log, Java and Kotlin println console log automatic capture, and automatic upload of symbol files.

Currently, there is another integration scheme for this situation, the solution is as follows:

// Application
@Override
public void onCreate() {
    super.onCreate();
    //Need to call before SDK initialization
    FTAutoTrack.startApp(null);
    //Set SDK configuration
    setSDK(this);
}
  //Application
    override fun onCreate() {
        super.onCreate()
    //Need to call before SDK initialization
        FTAutoTrack.startApp(null)
        //Set SDK configuration
        setSDK(this)

    }
  • Button and other events need to be added manually at the trigger point, for example, Button onClick event, source code example refer to ManualActivity.kt:
view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        FTRUMGlobalManager.get().startAction("[action button]", "click");
    }
});
    view.setOnClickListener{
        FTRUMGlobalManager.get().startAction("[action button]", "click")
    }
  • OKhttp integrates Resource, Trace through addInterceptor, eventListener, as shown below, source code example refer to ManualActivity.kt:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new FTTraceInterceptor())
.addInterceptor(new FTResourceInterceptor())
.eventListenerFactory(new FTResourceEventListener.FTFactory());
//.eventListenerFactory(new FTResourceEventListener.FTFactory(true));
OkHttpClient client = builder.build();
val builder = OkHttpClient.Builder()
.addInterceptor(FTTraceInterceptor())
.addInterceptor(FTResourceInterceptor())
.eventListenerFactory(FTResourceEventListener.FTFactory())
//.eventListenerFactory(new FTResourceEventListener.FTFactory(true))
val client = builder.build()
  • Other network frameworks need to manually implement the use of FTRUMGlobalManager startResource, stopResource, addResource, FTTraceManager.getTraceHeader. For specific implementation, refer to the source code example ManualActivity.kt

  • WebView Data Collection Configuration

FTAutoTrack.setUpWebView(webview)
//Configure before loadUrl
FTAutoTrack.setUpWebView(webview)
//Configure before loadUrl

Feedback

Is this page helpful? ×