属性插件化
属性插件化是神策 Android SDK 在 v6.4.3 及以后版本提供用于修改 track 类型埋点事件属性的高级功能,用户可通过属性插件实现指定事件的属性新增和删除、特定类型事件的属性新增和删除。
接口介绍
SDK 提供了 SAConfigOptions.registerPropertyPlugin() 和 SensorsDataAPI.registerPropertyPlugin() 注册自定义属性插件,通过实现自定义 SAPropertyPlugin 属性插件用于给指定的事件添加属性。注意在 SAConfigOptions 注册时机早能够确保筛选的事件都添加上对应属性,通过 SensorsDataAPI 注册对于延迟初始化触发的启动事件可能无法添加上对应属性。
SAPropertyPlugin 类介绍:
方法名 | 描述 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
properties(SAPropertiesFetcher fetcher) | 用于对属性进行修改,通过 fetcher.getProperties() 获取属性 JSONObject 集合,然后完成对属性的修改。 SAPropertiesFetcher 类的方法介绍:
| ||||||||||
isMatchedWithFilter(SAPropertyFilter filter) | 用于指定属性插件的生效范围,默认值对 track 类型事件生效。 SAPropertyFilter 类的方法介绍:
| ||||||||||
SAPropertyPluginPriority priority() | 属性插件优先级,默认为 Default(500),取值越高修改属性的等级越高。 | ||||||||||
getName() | 用于设置属性插件的名称,需确保名称唯一不可重复,默认取值为类全路径名称。 |
使用示例
在下面的示例中,筛选出指定事件,然后添加自定义属性。
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();
}
}
});
拦截事件入库
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;
}
});
自定义缓存加密
在基础 API 介绍文档中, SDK 缓存加密默认是 AES 加密方式,如果需要使用其它加密方式则需要通过缓存加密插件进行自定义。
接口介绍
在 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 是否存在存储文件中,需实现对应逻辑 |
在自定义缓存加密存储插件时,需要由开发者来实现对应类型数据的存储与获取。
使用示例
在下面的示例中,使用自定义加密插件实现国密算法加密。
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;
}
}
限制 SDK 读取敏感属性
该功能用于 App 端给 SDK 设置敏感的设备标识符取值,可用于解决设备标识符调用多次的问题。目前 SDK 中针对运营商、AndroidID、device_id、Mac 地址等信息都做了缓存,当获取成功时则使用缓存数据,获取为空会尝试再次获取。
Android SDK v6.8.0 及以上版本已删除获取 Mac 地址、获取 IMEI 号、获取 MEID 号、获取 运营商信息、获取 IMSI 信息的业务逻辑处理,即 SDK 中不涉及以上标识符的读取和使用。
该操作属于危险操作,使用前请咨询值班同学指导。
接口说明
接口名 | 参数说明 | 接口用处 |
---|---|---|
registerLimitKeys(Map | 设置限制性属性集合 | 通过设置限制性属性的预置值来代替 SDK 调用对应的接口读取获取,用于避免敏感 API 调用 |
Android 预置 LimitKey
名称 | 描述 |
---|---|
ANDROID_ID | AndroidID,用于 $device_id 属性采集和匿名状态下的用户标识(distinct_id、AnonymousId),AndroidID 被禁用时则使用 uuid 作为用户标识,危险操作,请确保传值的正确性。 |
OAID | oaid 用于渠道匹配。危险操作,请确保传值的正确性。 |
使用示例
注意
AndroidID 涉及用户标识,请务必确保传入正确。建议可通过查看神策获取 AndroidID 接口实现进行获取。
SAConfigOptions configOptions = new SAConfigOptions(SA_SERVER_URL);
// 设置限制性属性
Map 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)
禁止获取 Android ID
在 Android v6.7.10 及以后版本中,新增 SensorsDataUtils.enableAndroidId (boolean enabled) 接口用于控制 AndroidID 的采集开启和关闭。true 表示可采集。false 表示不可采集。
// 关闭采集 AndroidID
SensorsDataUtils.enableAndroidId(false);
禁止获取 OAID
在 Android v6.7.10 及以后版本中,新增 SensorsDataUtils.enableOAID (boolean enabled) 接口用于控制 OAID 的采集开启和关闭。true 表示可采集。false 表示不可采集。
// 关闭采集 OAID
SensorsDataUtils.enableOAID(false);
关闭屏幕方向传感器
在初始化 SDK 后,可主动开启/关闭屏幕方向采集。
// 关闭屏幕方向采集和方向传感器
SensorsDataAPI.sharedInstance().enableTrackScreenOrientation(false)