设备ID在我们日常业务追踪中和日志排查中起到了至关重要的作用,所以能在不同APP中、APP更新、卸载重装、手机重启、手机恢复默认设置后还能保持唯一就显得很有必要了,下面我们首先我们介绍下常用获取设备id的方法,以及他们之间的差异。
Android获取设备id的方式:
iOS中获取设备id的方式:
通过上面两张表格我们明显发现安卓的MAC地址、IMEI/MEID和ios的UDID是我门想要的不二之选;
不过很可以ios中UDID私有方法以及被苹果禁用,不允许上架使用了,所以无法使用;
**安卓的MAC地址、IMEI/MEID自从《个人信息保护法》发布和隐私合规限制,也不允许使用了;
**
那么留给我们的发挥的空间就很小了,基本上余下都在各个场景中会产生变化,这对我们的日志追踪会造成毁灭性的破坏,所以我门需要重新寻找替代方案,最小程度上杜绝设备id变化。
1、在iOS中我们可以使用钥匙串实现
我们想到可以使用UUID/IDFV生产一个临时的id,然后将其存储到系统的钥匙串中,在使用的时候从钥匙串中取值这样就能保持不同APP之间的设备ID唯一,同时不管APP卸载还是重启设备ID都不会变化。
当然这样也不是完全安全的,如果我们认为恢复出厂设置、重装系统等还是会删除钥匙串中的信息,只不过我们是将设备ID变化的可能降到最低。
代码
**@implementation LGShareUdidTool
**
- (instancetype)shareManager{
staticLGShareUdidTool* manager;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
manager = [[LGShareUdidToolalloc]init];
});
returnmanager;
}
- (NSString *) getDeviceUDID {
return [[LGShareUdidTool shareManager] getDeviceUDID];
}
- (NSString *) getDeviceUDID {
//中间变量防止多次读取 影响性能
if(self.deviceUdid.length>0) {
returnself.deviceUdid;
}
if ([self getKeychainDataForKey:kUdidKeychain]) {
self.deviceUdid = [self getKeychainDataForKey:kUdidKeychain];
if(self.deviceUdid.length>0) {
returnself.deviceUdid;
}
//如果有脏数据 则重新写入
self.deviceUdid = [UIDevice currentDevice].identifierForVendor.UUIDString;
[self addKeychainData:self.deviceUdid forKey:kUdidKeychain];
returnself.deviceUdid;
}else{
//写入
self.deviceUdid = [UIDevice currentDevice].identifierForVendor.UUIDString;
[self addKeychainData:self.deviceUdid forKey:kUdidKeychain];
returnself.deviceUdid;
}
}
//创建一个基本的查询字典
- (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass,
service, (__bridgeid)kSecAttrService,
service, (__bridgeid)kSecAttrAccount,
(__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible,
nil];
}
- (id)getKeychainDataForKey:(NSString *)key{
idret =nil;
NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuerysetObject:(id)kCFBooleanTrueforKey:(__bridgeid)kSecReturnData];
[keychainQuerysetObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRefkeyData =NULL;
if(SecItemCopyMatching((__bridgeCFDictionaryRef)keychainQuery, (CFTypeRef*)&keyData) ==noErr) {
@try{
ret = [NSKeyedUnarchiverunarchiveObjectWithData:(__bridgeNSData*)keyData];
}@catch(NSException *e) {
NSLog(@"Unarchive of %@ failed: %@",key,e);
}@finally{
}
}
if(keyData)
CFRelease(keyData);
returnret;
}
- (void)addKeychainData:(id)dataforKey:(NSString*)key{
//Get search dictionary
NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];
//Delete old item before add new item
SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuerysetObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((__bridgeCFDictionaryRef)keychainQuery,NULL);
}
- (void)addShareKeyChainData:(id)data forKey:(NSString *)key{
//Get search dictionary
NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];
[keychainQuerysetObject:kAccessGroupItemforKey:(id)kSecAttrAccessGroup];
//Delete old item before add new item
SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuerysetObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((__bridgeCFDictionaryRef)keychainQuery,NULL);
}
- (void)deleteKeychainDataForKey:(NSString *)key{
NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];
SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);
}
**2、在安卓中我们使用卓信ID
**
在合规中UDID、MAC地址被严格限制获取,在这里我门找到中国信通院提供的卓信ID,用来合规的替代获取设备的ID,感兴趣的可以自行去了解一下。
不过卓信ID也并不是一成不变的,卓信id通过手机硬件信息生成一个设备的唯一根id,再根id基础上暴露一个卓信id给用户,卓信id一个月更新一次。卓信id提供两个接口,一个是通过卓信id查设备上六个月内所有的历史卓信id,一个是对比六个月内任意两个id是否来至于同一设备,同时卓信ID也可能会获取失败。
在隐私合规中获取oaid(安卓11系统以上)是在允许范围内的,所以我们增加oaid和友盟ID来弥补卓信ID获取失败时的兜底
1、冷启动时调获取DeviceID接口,返回有效时间和DeviceID,本地缓存DeviceID
2、如果下次冷启动时效过了就重新请求DeviceID接口,否者不用重新请求
3、如果接口请求失败,重连一次,降低因为网络或者服务波动引起的接口请求失败,导致id获取失败
4、如果卓信id、oaid为空不必生成DeviceID,经过验证这种概率很低了。