各种获取设备唯一标识的方法介绍

各种获取设备唯一标识的方法介绍一.UDID(UniqueDeviceIdentifier)UDID的全称是UniqueDeviceIdentifier,它就是苹果iOS设备的唯一识别码,它由40位16进制数的字母和数字组成(越狱的设备通过某些工具可以改变设备的UDID)。移动网络可利用UDID来识别移动设备,但是,从IOS5.0(2011年8月份)开始,苹果宣布将不再支持用uniqueIdentifier方法获

大家好,又见面了,我是你们的朋友全栈君。

//现在苹果对隐私方面很严  很难获取一种较好的设备唯一标识符方法,下面几篇博文仅做参考:

使用UUID作为手机唯一标识符在app删除并重新安装后会重新生成。当我们把第一次生成的UUID保存到KeyChain中就能解决这个问题。 
推荐大家使用SFHFKeychainUtils来操作keychain。 
SFHFKeychainUtils的下载链接http://download.csdn.net/detail/u011173536/9452292

NSString *SERVICE_NAME = @"com.-----";//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
    if ([str length]<=0)
    {
        str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符
        [SFHFKeychainUtils storeUsername:@"UUID"
                             andPassword:str
                          forServiceName:SERVICE_NAME
                          updateExisting:1
                                   error:nil];  // 往keychain添加数据

 

一.UDID(Unique Device Identifier)

UDID的全称是Unique Device Identifier,它就是苹果iOS设备的唯一识别码,它由40位16进制数的字母和数字组成(越狱的设备通过某些工具可以改变设备的UDID)。移动网络可利用UDID来识别移动设备,但是,从IOS5.0(2011年8月份)开始,苹果宣布将不再支持用uniqueIdentifier方法获取设备的UDID,iOS5以下是可以用的。苹果从iOS5开始就移除了通过代码访问UDID的权限。从2013年5月1日起,试图访问UIDIDs的程序将不再被审核通过,替代的方案是开发者应该使用“在ios 6中介绍的Vendor或Advertising标示符”。所以UDID是绝对是不能再使用了。

//UUID , 已废除
NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];
  • 1
  • 2
  • 1
  • 2

为什么苹果反对开发人员使用UDID? 
iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。 许多开发者把UDID跟用户的真实姓名、密码、住址、其它数据关联起来;网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据。同时大部分应用确实在频繁传输UDID和私人信息。 为了避免集体诉讼,苹果最终决定在iOS 5 的时候,将这一惯例废除,开发者被引导生成一个唯一的标识符,只能检测应用程序,其他的信息不提供。现在应用试图获取UDID已被禁止且不允许上架。

二.UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的缩写,中文意思是通用唯一识别码。它是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。这样,每个人都可以建立不与其它人冲突的 UUID。在此情况下,就不需考虑数据库建立时的名称重复问题。苹果公司建议使用UUID为应用生成唯一标识字符串。 
获得的UUID值系统没有存储, 而且每次调用得到UUID,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到NSUserDefaults, Keychain, Pasteboard或其它地方。

1.CFUUID

从iOS2.0开始,CFUUID就已经出现了。它是CoreFoundatio包的一部分,因此API属于C语言风格。CFUUIDCreate 方法用来创建CFUUIDRef,并且可以获得一个相应的NSString,如下代码:

CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
  • 1
  • 1

获得的这个CFUUID值系统并没有存储。每次调用CFUUIDCreate,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到NSUserDefaults, Keychain, Pasteboard或其它地方。

2.NSUUID

NSUUID在iOS 6中才出现,这跟CFUUID几乎完全一样,只不过它是Objective-C接口。+ (id)UUID 是一个类方法,调用该方法可以获得一个UUID。通过下面的代码可以获得一个UUID字符串:

NSString *uuid = [[NSUUID UUID] UUIDString];
  • 1
  • 1

跟CFUUID一样,这个值系统也不会存储,每次调用的时候都会获得一个新的唯一标示符。如果要存储的话,你需要自己存储。在我读取NSUUID时,注意到获取到的这个值跟CFUUID完全一样(不过也可能不一样)

三.open UDID

在iOS 5发布时,uniqueIdentifier被弃用了,这引起了广大开发者需要寻找一个可以替代UDID,并且不受苹果控制的方案。由此OpenUDID成为了当时使用最广泛的开源UDID替代方案。OpenUDID在工程中实现起来非常简单,并且还支持一系列的广告提供商。

OpenUDID利用了一个非常巧妙的方法在不同程序间存储标示符 — 在粘贴板中用了一个特殊的名称来存储标示符。通过这种方法,别的程序(同样使用了OpenUDID)知道去什么地方获取已经生成的标示符(而不用再生成一个新的)。而且根据贡献者的代码和方法,和一些开发者的经验,如果把使用了OpenUDID方案的应用全部都删除,再重新获取OpenUDID,此时的OpenUDID就跟以前的不一样。可见,这种方法还是不保险。 
但是OpenUDID库早已经弃用了, 在其官方的博客中也指明了, 停止维护OpenUDID的原因是为了更好的向苹果的举措靠拢, 还指明了MAC Address不是一个好的选择。

四.MAC Address

1.这个MAC地址是指什么?有什么用?

MAC(Medium/Media Access Control)地址,用来表示互联网上每一个站点的标识符,采用十六进制数表示,共六个字节(48位)。其中,前三个字节是由IEEE的注册管理机构 RA负责给不同厂家分配的代码(高位24位),也称为“编制上唯一的标识符” (Organizationally Unique Identifier),后三个字节(低位24位)由各厂家自行指派给生产的适配器接口,称为扩展标识符(唯一性)。 
MAC地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是不同的,是唯一的。一部iPhone上可能有多个MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一个WIFI的,因此只需获取WIFI的MAC地址就好了,也就是en0的地址。 
形象的说,MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。这样就可以非常好的标识设备唯一性,类似与苹果设备的UDID号,通常的用途有: 
1)用于一些统计与分析目的,利用用户的操作习惯和数据更好的规划产品; 
2)作为用户ID来唯一识别用户,可以用游客身份使用app又能在服务器端保存相应的信息,省去用户名、密码等注册过程。

2.如何使用Mac地址生成设备的唯一标识呢?

主要分三种: 
1、直接使用“MAC Address” 
2、使用“MD5(MAC Address)” 
3、使用“MD5(Mac Address+bundle_id)”获得“机器+应用”的唯一标识(bundle_id 是应用的唯一标识)

iOS7之前,因为Mac地址是唯一的, 一般app开发者会采取第3种方式来识别安装对应app的设备。为什么会使用它?在iOS5之前,都是使用UDID的,后来被禁用。苹果推荐使用UUID 但是也有诸多问题,从而使用MAC地址。而MAC地址跟UDID一样,存在隐私问题,现在苹果新发布的iOS7上,如果请求Mac地址都会返回一个固定值,那么Mac Address+bundle_id这个值大家的设备都变成一致的啦,跟UDID一样相当于被禁用, 所以Mac Address 是不能够被使用为获取设备唯一标识的。

五.广告标示符(IDFA-identifierForIdentifier)

广告标示符,在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的。但好在Apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测推广效果,是戳戳有余了。 
它是iOS 6中另外一个新的方法,提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例,最后可以获得一个UUID,由系统存储着的。

#import <AdSupport/AdSupport.h>
    NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
  • 1
  • 2
  • 1
  • 2

不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。 
关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广 告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。 
所以IDFA也不可以作为获取唯一标识的方法,来识别用户。

六.Vendor标示符 (IDFV-identifierForVendor)

Vendor标示符,是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.taobao.app1, com.taobao.app2 这两个BundleID来说,就属于同一个Vender,共享同一个IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。 
它是iOS 6中新增的,跟advertisingIdentifier一样,该方法返回的是一个 NSUUID对象,可以获得一个UUID。如果满足条件“相同的一个程序里面-相同的vendor-相同的设备”,那么获取到的这个属性值就不会变。如果是“相同的程序-相同的设备-不同的vendor,或者是相同的程序-不同的设备-无论是否相同的vendor”这样的情况,那么这个值是不会相同的。

    NSString *strIDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
  • 1
  • 1

但是如果用户将属于此Vender的所有App卸载,则IDFV的值会被重置,即再重装此Vender的App,IDFV的值和之前不同。

七.推送token+bundle_id

推送token+bundle_id的方法: 
1、应用中增加推送用来获取token 
2、获取应用bundle_id 
3、根据token+bundle_id进行散列运算

apple push token保证设备唯一,但必须有网络情况下才能工作,该方法并不是依赖于设备本身,而是依赖于apple push机制,所以当苹果push做出改变时, 你获取所谓的唯一标识也就随之失效了。所以此方法还是不可取的。

八. NSUUID, CFUUID, IDFA, IDFV获取的标识对比

首次运行

NSUUID:9D820D3A-4429-4918-97F7-A69588B388A4 
CFUUID:80F961D0-1E6A-4ECD-A0A9-F58ED858FE20 
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD 
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488

二次运行

NSUUID:23AB8D3D-4F1D-45E2-8BD7-83B451125326 
CFUUID:14DDBFCF-67A6-46B7-BB48-4EF2ADC5429F 
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD 
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488

卸载后, 重新安装运行

NSUUID:BD934F9C-B7EC-4BD1-B65E-964C66537CAB 
CFUUID:29654DE0-AC93-40F9-98AB-1E10A271AF8D 
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD 
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488

重启后运行

NSUUID:82711557-3A17-4B82-8F18-09AADF9DD37B 
CFUUID:FFBC73EC-CFBE-414C-870E-77C0714E0347 
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD 
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488

总结

说了这么多, 才发现原来没有一种方法是可行的。没错, 其实自从苹果废除UDID后, 就不能达到获取设备真正的唯一标识了。因为这些方法中导致获取的唯一标示产生改变的原因, 或是重新调用方法, 或是重启设备, 或是卸载应用, 或是还原某些标识, 或者刷新系统… 
所以, 不能达到从根本上获取唯一标识, 我们只能做到尽可能接近。下面是我用过的方法。

如何正确的获取设备的唯一标识

我用的方法是将获取的UUID永久存储在设备的KeyChain中, 这个方法在应用第一次启动时, 将获取的UUID存储进KeyChain中, 每次取的时候, 检查本地钥匙串中有没有, 如果没有则需要将获取的UUID存储进去。当你重启设备, 卸载应用再次安装,都不影响, 只是当设备刷机时, KeyChain会清空, 才会消失, 才会失效。 
不只是这一种方法, 你也可以保存除UUID之外,其他合适的标识, 但利用KeyChain去存储标识的方式应该是最接近的。

由于苹果对安全的加强,现在通过

NSString *identifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; 

获取唯一标示的方法不可行了。 
需要将唯一标示保存到KeyChain中,这样即便是应用卸载了,然后用户在安装也是获取到的唯一的UUID。

我封装了一个库,下载地址: 
http://download.csdn.net/detail/zhuzhiqiang_zhu/9693729

需要设置的东西: 
这里写图片描述

使用起来非常的方便,

    //保存到keychain
    if([AppUntils readUUIDFromKeyChain]){
        [AppUntils saveUUIDToKeyChain];
    }

获取

NSString *adUuid = [AppUntils readUUIDFromKeyChain];

 keyChain + uuid

方式使用 keyChain + uuid
1.需要在xcode -> project ->target ->capabilities 中添加 keychain sharing
2.在项目中添加下面两个文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface DeviceId : NSObject
+ (NSString *)getUUIDByKeyChain;
+ (void)save:(NSString*)service data:(id)data;
+ (id)load:(NSString*)service;
+ (void)deleteKeyData:(NSString*)service;
@end

NS_ASSUME_NONNULL_END
#import "DeviceId.h"
#import <UIKit/UIKit.h>
@implementation DeviceId


/**  获取UUID*/
+ (NSString *)getUUIDByKeyChain{
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];//获取app版本信息
    NSString *key = [NSString stringWithFormat:@"%@.uniqueid",[infoDictionary objectForKey:@"CFBundleIdentifier"]];
    NSString*strUUID = (NSString*)[DeviceId load:key];
    if([strUUID isEqualToString:@""]|| !strUUID)
    {
        strUUID = [UIDevice currentDevice].identifierForVendor.UUIDString;
        
        if(strUUID.length ==0 || [strUUID isEqualToString:@"00000000-0000-0000-0000-000000000000"])
        {
            CFUUIDRef uuidRef= CFUUIDCreate(kCFAllocatorDefault);
            strUUID = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault,uuidRef));
            CFRelease(uuidRef);
        }
        //将该uuid保存到keychain
        [DeviceId save:key data:strUUID];
    }
    return strUUID;
}


+ (NSMutableDictionary*)getKeychainQuery:(NSString*)service {
    return[NSMutableDictionary dictionaryWithObjectsAndKeys:
           (id)kSecClassGenericPassword,(id)kSecClass,
           service,(id)kSecAttrService,
           service,(id)kSecAttrAccount,
           (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
           nil];
}
 
+ (void)save:(NSString*)service data:(id)data{
    //Get search dictionary
    NSMutableDictionary*keychainQuery = [self getKeychainQuery:service];
    //Delete old item before add new item
    SecItemDelete((CFDictionaryRef)keychainQuery);
    //Add new object to searchdictionary(Attention:the data format)
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data]forKey:(id)kSecValueData];
    //Add item to keychain with the searchdictionary
    SecItemAdd((CFDictionaryRef)keychainQuery,NULL);
}
 
+ (id)load:(NSString*)service {
    id ret =nil;
    NSMutableDictionary*keychainQuery = [self getKeychainQuery:service];
    //Configure the search setting
    //Since in our simple case we areexpecting only a single attribute to be returned (the password) wecan set the attribute kSecReturnData to kCFBooleanTrue
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData =NULL;
    if(SecItemCopyMatching((CFDictionaryRef)keychainQuery,(CFTypeRef*)&keyData) ==noErr){
        @try{
            ret =[NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData*)keyData];
        }@catch(NSException *e) {
            NSLog(@"Unarchiveof %@ failed: %@",service, e);
        }@finally{
        }
    }
    if(keyData)
        CFRelease(keyData);
    return ret;
}
 
+ (void)deleteKeyData:(NSString*)service {
    NSMutableDictionary*keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
    NSString *uuid = [DeviceId getUUIDByKeyChain];
    NSLog(@"uuid = %@",uuid);

原文链接:  https://www.jianshu.com/p/408977444043

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/157206.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • nextline函数_nextLine()和next()的区别和使用方法

    nextline函数_nextLine()和next()的区别和使用方法最近在笔试,刷剑指Offer时,都是只需要把方法实现了就行。但是!!!笔试时候会发现,大部分会要求你把main函数也code出来,真是醉了,第一次笔试时候搞的晕乎乎的…..废话不多说,那么在写输入输出中,肯定要用到Scanner类了,其中不少都需要读取一个整数或者一个整型数组。当读入整数时(以int为例),直接就nextInt()就好,可是当读入一个整型数组时(数字之间用空格隔开),就涉及到用…

  • 网管工具软件_netscan

    网管工具软件_netscan转发:http://baike.baidu.com/view/1358799.htmCacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具。Cacti是通过snmpget来获取数据,使用RRDtool绘画图形,而且你完全可以不需要了解RRDtool复杂的参数。它提供了非常强大的数据和用户管理功能,可以指定每一个用户能查看树状结构、

  • 卸载vs2012的步骤_如何卸载vs2010

    卸载vs2012的步骤_如何卸载vs2010       同时安装SQLServer2000和VS.NET2005以后,企业管理器就打不开了,那也难怪,它和SQLServer2005才是绝配,没办法,只好卸载掉VS.NET2005,装VS.NET2003,但是卸载的时候在控制面板里面如果卸载的顺序不对的话,可能卸载不干净,有了几次这样的经历之后,把大致顺序总结如下:1、Microsoft Visual Studio 2005

  • php 工厂方法模式

    php 工厂方法模式

  • silverlight获取web的url参数

    silverlight获取web的url参数

  • python中删除列表中重复元素

    python中删除列表中重复元素在面试中,很可能遇到给定一个含有重复元素的列表,删除其中重复的元素,下边给出三种方法来实现这个功能。1.使用内置函数setlists=[1,1,2,3,4,6,6,2,2,9]lists=list(set(lists))先将列表转换为集合,因为集合是不重复的,故直接删除重复元素,而且输出结果为排序后的2.使用del函数或者remove函数lists=[1,1…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号