1. 属性插件化

属性插件化是神策 Android SDK 在 v6.4.3 及以后版本提供用于修改 track 类型埋点事件属性的高级功能,用户可通过属性插件实现指定事件的属性新增和删除、特定类型事件的属性新增和删除。

1.1. 接口介绍

SDK 提供了 SAConfigOptions.registerPropertyPlugin() 和 SensorsDataAPI.registerPropertyPlugin() 注册自定义属性插件,通过实现自定义 SAPropertyPlugin 属性插件用于给指定的事件添加属性。注意在 SAConfigOptions 注册时机早能够确保筛选的事件都添加上对应属性,通过 SensorsDataAPI 注册对于延迟初始化触发的启动事件可能无法添加上对应属性。

SAPropertyPlugin 类介绍:

方法名描述
properties(SAPropertiesFetcher fetcher)

用于对属性进行修改,通过 fetcher.getProperties() 获取属性 JSONObject 集合,然后完成对属性的修改。

SAPropertiesFetcher 类的方法介绍:

方法名描述
JSONObject getProperties()获取 properties 属性对应的 JSONObject 集合。操作 JSONObject 可进行属性增删改查。
JSONObject getEventJson(String name)                         

根据指定的 name 名称获取对应的属性 JSONObject 集合,目前支持的 name 取值为:SAPropertyFilter.PROPERTIES、SAPropertyFilter.LIB 和 SAPropertyFilter.IDENTITIES。

isMatchedWithFilter(SAPropertyFilter filter)

用于指定属性插件的生效范围,默认值对 track 类型事件生效。

SAPropertyFilter 类的方法介绍:

方法名描述
String getEvent()          获取事件名,可根据事件名进行筛选
EventType getType()获取事件类型,取值为 track、track_signup、track_id_bind、track_id_unbind、profile_set、profile_set_once、profile_unset、profile_increment、profile_append、profile_delete、item_set 和 item_delete。
JSONObject getProperties()               获取当前事件的属性,可用于以属性为筛选条件的场景
JSONObject getEventJson(String name)根据指定的 name 名称获取对应的属性 JSONObject 集合,目前支持的 name 取值为:SAPropertyFilter.PROPERTIES、SAPropertyFilter.LIB 和 SAPropertyFilter.IDENTITIES。
SAPropertyPluginPriority priority()属性插件优先级,默认为 Default(500),取值越高修改属性的等级越高。
getName()用于设置属性插件的名称,需确保名称唯一不可重复,默认取值为类全路径名称。

1.2. 使用示例

在下面的示例中,筛选出指定事件,然后添加自定义属性。

saConfigOptions.registerPropertyPlugin(new SAPropertyPlugin() {
    @Override
    public boolean isMatchedWithFilter(SAPropertyFilter filter) {
        // 示例,筛选指定事件名添加属性,默认筛选条件是 track 事件类型
        return "指定事件名称".equals(filter.getEvent());
    }

    @Override
    public void properties(SAPropertiesFetcher fetcher) {
        try {
            // 给符合筛选条件的事件添加属性
            fetcher.getProperties().put("自定义属性","自定义属性名称");
			//fetcher.getProperties().remove(key) 删除执行属性
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
});
JAVA

2. 拦截事件入库

v3.2.10 及以后版本,SDK 可以通过 setTrackEventCallBack() 方法过滤埋点的事件数据。SDK 根据 onTrackEvent 接口的返回值决定埋点事件是否入库,返回 true 则当前事件会入库并正常上报,返回 false 则表示丢弃该条事件。

SensorsDataAPI.sharedInstance().setTrackEventCallBack(new SensorsDataTrackEventCallBack() {
	/**
     * 事件回调接口
     *
     * @param eventName 事件名称
     * @param eventProperties 要修改的事件属性
     * @return true 表示事件将入库, false 表示事件将被抛弃
     */
	@Override
	public boolean onTrackEvent(String eventName, JSONObject eventProperties) {
	
		if(eventName.equals("BuyProduct")){
			// 不要这个埋点了
            returen false;
		}	
		//
		return true;
	}
});
JAVA

3. 自定义缓存加密

在基础 API 介绍文档中, SDK 缓存加密默认是 AES 加密方式,如果需要使用其它加密方式则需要通过缓存加密插件进行自定义。

3.1. 接口介绍

在 SDK 初始化时调用 registerStorePlugin 接口注册自定义加密插件即可开启。

StorePlugin 类介绍:

方法名描述
setString(String key, String value)存储 String 类型数据,需要实现对应的存储逻辑
getString(String key)根据指定 key 获取 String 类型数据,需实现获取逻辑
setBool(String key, boolean value)存储 bool 类型数据,需要实现对应的存储逻辑
getBool(String key)根据指定 key 获取 bool 类型数据,需实现获取逻辑
setInteger(String key, int value)存储 int 类型数据,需要实现对应的存储逻辑
getInteger(String key)根据指定 key 获取 int 类型数据,需实现获取逻辑
setFloat(String key, float value)存储 float 类型数据,需要实现对应的存储逻辑
getFloat(String key)根据指定 key 获取 float 类型数据,需实现获取逻辑
setLong(String key, long value)存储 long 类型数据,需要实现对应的存储逻辑
getLong(String key)根据指定 key 获取 long 类型数据,需实现获取逻辑
remove(String key)删除指定 key,需实现对应逻辑
type()属性插件名称,需实现对应逻辑
isExists(String key)判断指定 key 是否存在存储文件中,需实现对应逻辑

在自定义缓存加密存储插件时,需要由开发者来实现对应类型数据的存储与获取。

3.2. 使用示例

在下面的示例中,使用自定义加密插件实现国密算法加密。

public class SM4StorePlugin implements StorePlugin {
    private static final String FILE_NAME = "com.sensorsdata.storePlugin";
    private static final String SM4_SECRET = "sm4_key";
    private byte[] mSM4KeyBytes;
    private final SharedPreferences mSPreferences;

    static {
        try {
            Class<?> provider = Class.forName("org.spongycastle.jce.provider.BouncyCastleProvider");
            Security.addProvider((Provider) provider.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SM4StorePlugin(Context context) {
        mSPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        try {
            if (mSPreferences.contains(SM4_SECRET)) {
                mSM4KeyBytes = Base64Coder.decode(mSPreferences.getString(SM4_SECRET, null));
            }
            if (mSM4KeyBytes == null) {
                mSM4KeyBytes = generateSymmetricKey();
                mSPreferences.edit().putString(SM4_SECRET, new String(Base64Coder.encode(mSM4KeyBytes))).apply();
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void upgrade(StorePlugin oldPlugin) {
        // 升级时的处理操作
    }

    @Override
    public void setString(String key, String value) {
        mSPreferences.edit().putString(key, sm4Encrypt(value)).apply();
    }

    @Override
    public void setBool(String key, boolean value) {
        mSPreferences.edit().putBoolean(key, value).apply();
    }

    @Override
    public void setInteger(String key, int value) {
        mSPreferences.edit().putInt(key, value).apply();
    }

    @Override
    public void setFloat(String key, float value) {
        mSPreferences.edit().putFloat(key, value).apply();
    }

    @Override
    public void setLong(String key, long value) {
        mSPreferences.edit().putLong(key, value).apply();
    }

    @Override
    public String getString(String key) {
        return sm4Decrypt(mSPreferences.getString(key, null));
    }

    @Override
    public Boolean getBool(String key) {
        return mSPreferences.getBoolean(key, false);
    }

    @Override
    public Integer getInteger(String key) {
        return mSPreferences.getInt(key, 0);
    }

    @Override
    public Float getFloat(String key) {
        return mSPreferences.getFloat(key, 0);
    }

    @Override
    public Long getLong(String key) {
        return mSPreferences.getLong(key, 0);
    }

    @Override
    public void remove(String key) {
        mSPreferences.edit().remove(key).apply();
    }

    @Override
    public boolean isExists(String key) {
        return mSPreferences.contains(key);
    }

    @Override
    public String type() {
        return FILE_NAME;
    }

    private byte[] generateSymmetricKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGen = KeyGenerator.getInstance("SM4");
        keyGen.init(128);
        SecretKey aesKey = keyGen.generateKey();
        return aesKey.getEncoded();
    }

    /**
     * 使用 SM4 进行加密
     * @return 加密后的数据
     */
    private String sm4Encrypt(String content) {
        if (mSM4KeyBytes == null) {
            return content;
        }
        try {
            Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
            byte[] ivBytes = new byte[16];
            SecretKeySpec secretKeySpec = new SecretKeySpec(mSM4KeyBytes, "SM4");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(ivBytes));

            byte[] encryptedBytes = cipher.doFinal(content.getBytes("UTF-8"));
            return new String(Base64.encode(encryptedBytes));
        } catch (Exception ex) {
            SALog.printStackTrace(ex);
        }
        return content;
    }

    private String sm4Decrypt(String content) {
        try {
            if (mSM4KeyBytes == null) {
                return content;
            }
            Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
            byte[] ivBytes = new byte[16];
            SecretKeySpec secretKeySpec = new SecretKeySpec(mSM4KeyBytes, "SM4");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(ivBytes));
            return new String(cipher.doFinal(Base64Coder.decode(content)), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return content;
    }
}
JAVA

4. 限制 SDK 读取敏感属性

该功能用于 App 端给 SDK 设置敏感的设备标识符取值,可用于解决设备标识符调用多次的问题。目前 SDK 中针对运营商、AndroidID、device_id、Mac 地址等信息都做了缓存,当获取成功时则使用缓存数据,获取为空会尝试再次获取。

Android SDK v6.8.0 及以上版本已删除获取 Mac 地址、获取  IMEI 号、获取  MEID 号、获取 运营商信息、获取 IMSI 信息的业务逻辑处理,即 SDK 中不涉及以上标识符的读取和使用。

该操作属于危险操作,使用前请咨询值班同学指导。

4.1. 接口说明

接口名

参数说明

接口用处

registerLimitKeys(Map<String, String> limitKeys)

设置限制性属性集合通过设置限制性属性的预置值来代替 SDK 调用对应的接口读取获取,用于避免敏感 API 调用

Android 预置 LimitKey

名称

描述

ANDROID_IDAndroidID,用于 $device_id 属性采集和匿名状态下的用户标识(distinct_id、AnonymousId),AndroidID 被禁用时则使用 uuid 作为用户标识,危险操作,请确保传值的正确性。
OAIDoaid 用于渠道匹配。危险操作,请确保传值的正确性。

4.2. 使用示例

注意

AndroidID 涉及用户标识,请务必确保传入正确。建议可通过查看神策获取 AndroidID 接口实现进行获取。

SAConfigOptions configOptions = new SAConfigOptions(SA_SERVER_URL);
// 设置限制性属性
Map<String, String> map = new HashMap<>();
map.put(LimitKey.ANDROID_ID, SensorsDataUtils.getIdentifier(this));
map.put(LimitKey.CARRIER, SensorsDataUtils.getOperator(this));
configOptions.registerLimitKeys(map);
//传入 SAConfigOptions 对象
SensorsDataAPI.startWithConfigOptions(this, configOptions);

//通过接口动态设置
SensorsDataAPI.sharedInstance().registerLimitKeys(map)
CODE

5. 禁止获取 Android ID

在 Android v6.7.10 及以后版本中,新增 SensorsDataUtils.enableAndroidId (boolean enabled) 接口用于控制 AndroidID 的采集开启和关闭。true 表示可采集。false 表示不可采集。

// 关闭采集 AndroidID
SensorsDataUtils.enableAndroidId(false);
CODE

6. 禁止获取 OAID

在 Android v6.7.10 及以后版本中,新增 SensorsDataUtils.enableOAID (boolean enabled) 接口用于控制 OAID 的采集开启和关闭。true 表示可采集。false 表示不可采集。

// 关闭采集 OAID
SensorsDataUtils.enableOAID(false);
CODE

7. 关闭屏幕方向传感器

在初始化 SDK 后,可主动开启/关闭屏幕方向采集。

// 关闭屏幕方向采集和方向传感器
SensorsDataAPI.sharedInstance().enableTrackScreenOrientation(false)
CODE