1. 属性插件化

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

1.1. 接口介绍

SDK 提供了 SAConfigOptions.registerPropertyPlugin() 初始化时和 SensorsDataAPI.registerPropertyPlugin() 初始化后注册自定义属性插件,通过实现自定义 SAPropertyPlugin 属性插件用于给指定的事件添加属性。

SAPropertyPlugin 类介绍:

方法名描述
properties(SAPropertiesFetcher fetcher)

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

SAPropertiesFetcher 类的方法介绍:

方法名描述
JSONObject getProperties()获取 properties 属性对应的 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. 使用示例

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

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

    @Override
    public void properties(SAPropertiesFetcher fetcher) {
        try {
            // 给符合筛选条件的事件添加属性
            fetcher.getProperties().put("自定义属性","自定义属性名称");
        } 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) {
		// 移除 BuyProduct 事件的 productID 属性
		if(eventName.equals("BuyProduct")){
			eventProperties.remove("productID");
		}	
		//
		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