设备数据检索指南
用于检索平台特定设备标识符和参数的综合指南,这些设备标识符和参数是准确的 S2S API 归因和营销活动测量所必需的。
所需的设备标识符:Singular 要求在所有 API 请求中提供特定的设备标识符,以便准确归因。
移动平台:
- 安卓(Google Play):谷歌广告 ID (GAID/AIFA) 和应用程序集 ID (ASID)
- 安卓(亚马逊):用于 Fire 设备的亚马逊广告 ID (AMID)
- 安卓(中国 OEM):开放广告 ID (OAID),适用于无 Google Play 服务的设备
- 安卓(回退):安卓 ID (ANDI) 仅在无其他可用标识符时使用
- iOS:供应商标识符 (IDFV) 和广告商标识符 (IDFA) 可用时
设备参数:移动平台所需的地域、设备制造商、设备型号和构建版本
下面的代码示例演示了每种平台和标识符类型的检索方法。
应用示例
参考实现
展示设备数据检索模式的 iOS 和 Android 完整工作示例。
iOS 设备标识符
iOS 设备需要 IDFV(始终)和 IDFA(当用户授予跟踪权限时)以及 ATT 授权状态,以便准确归属。
所需的 iOS 标识符
标识符要求:
- IDFV:所有 S2S 请求都需要,无论跟踪权限如何
- IDFA:如果用户同意应用跟踪透明度,则应提供
- ATT 状态:所有请求都需要的授权状态代码(0-3)
实施指南
广告商识别码 (IDFA)
广告商识别码 (IDFA) 使广告商能够跟踪用户操作(广告点击、应用安装)并将其归属于特定广告系列,从而实现精准定位和优化。
从 iOS 14.5 开始,用户必须在应用程序访问 IDFA 之前通过应用程序跟踪透明度 (ATT) 框架进行选择。未经用户同意,IDFA 将返回所有 0,从而限制了跟踪功能。
供应商标识符 (IDFV)
供应商标识符 (IDFV) 是 Apple 分配给设备的唯一标识符,特定于供应商/开发者。在设备上来自同一供应商的所有应用程序中保持一致,无需个人身份识别即可实现跨应用程序行为跟踪。
实施步骤:
- 确保在尝试访问 IDFA 之前显示并处理 ATT 提示
- 捕获 IDFA(如果已授权)并传递给服务器以进行 API 请求
- 捕获 IDFV 并传递给服务器以进行 API 请求(始终需要
- 在所有请求中包含 ATT 授权状态
代码示例
请求 ATT 授权并检索 IDFA/IDFV
#import <AdSupport/AdSupport.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <UIKit/UIKit.h>
- (void)retrieveIdentifiers {
// Request ATT authorization (iOS 14.5+)
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
switch (status) {
case ATTrackingManagerAuthorizationStatusAuthorized: {
// ATT authorized, retrieve IDFA
NSUUID *idfa = [[ASIdentifierManager sharedManager] advertisingIdentifier];
NSLog(@"IDFA: %@", [idfa UUIDString]);
NSLog(@"ATT Status: %ld", (long)status); // Status = 3
break;
}
case ATTrackingManagerAuthorizationStatusDenied:
NSLog(@"ATT Status: Denied (%ld)", (long)status); // Status = 2
break;
case ATTrackingManagerAuthorizationStatusRestricted:
NSLog(@"ATT Status: Restricted (%ld)", (long)status); // Status = 1
break;
case ATTrackingManagerAuthorizationStatusNotDetermined:
NSLog(@"ATT Status: Not Determined (%ld)", (long)status); // Status = 0
break;
default:
NSLog(@"Unknown ATT status.");
break;
}
// Retrieve IDFV (always available)
NSUUID *idfv = [[UIDevice currentDevice] identifierForVendor];
if (idfv != nil) {
NSLog(@"IDFV: %@", [idfv UUIDString]);
} else {
NSLog(@"Unable to retrieve IDFV.");
}
});
}];
}
// Call the method to retrieve identifiers
[self retrieveIdentifiers];
import AdSupport
import AppTrackingTransparency
import UIKit
func retrieveIdentifiers() {
// Request ATT authorization (iOS 14.5+)
ATTrackingManager.requestTrackingAuthorization { status in
DispatchQueue.main.async {
switch status {
case .authorized:
// ATT authorized, retrieve IDFA
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
print("IDFA: \(idfa)")
print("ATT Status: \(status.rawValue)") // Status = 3
case .denied:
print("ATT Status: Denied (\(status.rawValue))") // Status = 2
case .restricted:
print("ATT Status: Restricted (\(status.rawValue))") // Status = 1
case .notDetermined:
print("ATT Status: Not Determined (\(status.rawValue))") // Status = 0
@unknown default:
print("Unknown ATT status.")
}
// Retrieve IDFV (always available)
if let idfv = UIDevice.current.identifierForVendor?.uuidString {
print("IDFV: \(idfv)")
} else {
print("Unable to retrieve IDFV.")
}
}
}
}
// Call the function to retrieve identifiers
retrieveIdentifiers()
标识符可用性:
- IDFA:需要 iOS 14.5 以上版本的 ATT 授权。未经同意,返回全零
- IDFV:始终可用--包含在所有 Singular API 请求中
- ATT 状态值:0=未确定,1=受限,2=拒绝,3=授权
安卓设备标识符(Google Play)
使用 Google Play 服务的安卓设备要求在所有请求中提供应用程序集 ID (ASID),并在可用时提供 Google 广告 ID (GAID/AIFA)。
所需的 Google Play 标识符
标识符要求:
- ASID:Google Play 设备的所有 S2S 请求都需要
- AIFA/GAID:可用时应提供(不可选择不提供
实施指南
谷歌广告标识符 (GAID)
谷歌广告标识符 (GAID),也称为 AIFA 或 Android Advertising ID (AAID),是分配给 Android 设备的唯一、用户可重置的标识符。 它使广告商和开发商能够跟踪和归因用户在应用程序中的操作,以便在维护隐私的同时进行广告定位和优化。
依赖关系
在build.gradle 中添加所需的依赖关系:
dependencies {
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
}
权限
如果针对 Android 12/API 31+ 级,请在AndroidManifest.xml 中添加权限:
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
使用
AdIdUtils.getGoogleAdId(getApplicationContext());
实现
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
public class AdIdUtils {
public static void getGoogleAdId(Context context) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
String adId = adInfo.getId();
boolean isLimitAdTrackingEnabled = adInfo.isLimitAdTrackingEnabled();
Log.d("GoogleAdID", "Advertising ID: " + adId);
Log.d("GoogleAdID", "Limit Ad Tracking: " + isLimitAdTrackingEnabled);
} catch (Exception e) {
Log.e("GoogleAdID", "Error retrieving GAID", e);
}
}
});
}
}
依赖关系
dependencies {
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
}
权限
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
使用方法
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
val googleAdId = AdIdUtils.getGoogleAdId(applicationContext)
Log.d("MainActivity", "Retrieved Google Ad ID: $googleAdId")
}
}
}
执行
import android.content.Context
import android.util.Log
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object AdIdUtils {
suspend fun getGoogleAdId(context: Context): String? {
return withContext(Dispatchers.IO) {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context)
val adId = adInfo.id
val isLimitAdTrackingEnabled = adInfo.isLimitAdTrackingEnabled
Log.d("GoogleAdID", "Advertising ID: $adId")
Log.d("GoogleAdID", "Limit Ad Tracking: $isLimitAdTrackingEnabled")
adId
} catch (e: Exception) {
Log.e("GoogleAdID", "Error retrieving GAID", e)
null
}
}
}
}
应用程序集 ID (ASID)
Android 应用程序集 ID 为同一开发者提供具有隐私意识的跨应用程序跟踪功能,可用于分析和防止欺诈,但不能用于个性化广告。
依赖关系
dependencies {
implementation 'com.google.android.gms:play-services-appset:16.1.0'
}
使用方法
AppSetIdUtils.getAppSetId(getApplicationContext());
实现
import android.content.Context;
import android.util.Log;
import com.google.android.gms.appset.AppSet;
import com.google.android.gms.appset.AppSetIdClient;
import com.google.android.gms.appset.AppSetIdInfo;
import com.google.android.gms.tasks.Task;
public class AppSetIdUtils {
public static void getAppSetId(Context context) {
AppSetIdClient client = AppSet.getClient(context);
Task task = client.getAppSetIdInfo();
task.addOnSuccessListener(info - {
String appSetId = info.getId();
int scope = info.getScope();
Log.d("AppSetID", "App Set ID: " + appSetId);
Log.d("AppSetID", "Scope: " + (scope == AppSetIdInfo.SCOPE_DEVELOPER ? "Developer" : "App"));
}).addOnFailureListener(e - {
Log.e("AppSetID", "Failed to retrieve App Set ID", e);
});
}
}
依赖关系
dependencies {
implementation 'com.google.android.gms:play-services-appset:16.1.0'
}
使用
AppSetIdUtils.getAppSetId(applicationContext)
实现
import android.content.Context
import android.util.Log
import com.google.android.gms.appset.AppSet
import com.google.android.gms.appset.AppSetIdClient
import com.google.android.gms.appset.AppSetIdInfo
object AppSetIdUtils {
fun getAppSetId(context: Context) {
val client: AppSetIdClient = AppSet.getClient(context)
val task = client.appSetIdInfo
task.addOnSuccessListener { info -
val appSetId: String = info.id
val scope: Int = info.scope
Log.d("AppSetID", "App Set ID: $appSetId")
Log.d("AppSetID", "Scope: ${if (scope == AppSetIdInfo.SCOPE_DEVELOPER) "Developer" else "App"}")
}.addOnFailureListener { exception -
Log.e("AppSetID", "Failed to retrieve App Set ID", exception)
}
}
}
安卓设备标识符(非 Google Play)
没有 Google Play 服务的安卓设备需要根据设备制造商和分发方式提供其他标识符。
亚马逊设备标识符
AMID:对于没有 Google Play 服务的 Amazon Fire 设备,应提供 Amazon Advertising ID。
亚马逊标识符 (AMID)
亚马逊广告标识符可在没有 Google Play 服务的亚马逊 Fire 设备上实现用户可重置的广告跟踪,在维护用户隐私的同时实现归因。
要求:
- 适用于运行 Fire OS 5.1+ 的亚马逊 Fire 设备
- 尊重用户的 "限制广告跟踪 "偏好
- 可能不适用于非 Fire OS 设备
使用方法
AdvertisingIdHelper.getAmazonAdvertisingId(getContentResolver());
实施
import android.content.ContentResolver;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
public class AdvertisingIdHelper {
public static void getAmazonAdvertisingId(ContentResolver contentResolver) {
String advertisingID = "";
boolean limitAdTracking = false;
try {
limitAdTracking = Settings.Secure.getInt(contentResolver, "limit_ad_tracking") != 0;
advertisingID = Settings.Secure.getString(contentResolver, "advertising_id");
Log.d("AdvertisingID", "Amazon Advertising ID: " + advertisingID);
Log.d("LimitAdTracking", "Limit Ad Tracking: " + limitAdTracking);
} catch (SettingNotFoundException e) {
Log.e("AdvertisingID", "Advertising ID not supported on this device", e);
}
}
}
使用方法
AdvertisingIdHelper.getAmazonAdvertisingId(contentResolver)
实施
import android.content.ContentResolver
import android.provider.Settings
import android.util.Log
object AdvertisingIdHelper {
fun getAmazonAdvertisingId(contentResolver: ContentResolver) {
try {
val limitAdTracking = Settings.Secure.getInt(contentResolver, "limit_ad_tracking") != 0
val advertisingID = Settings.Secure.getString(contentResolver, "advertising_id")
Log.d("AdvertisingID", "Amazon Advertising ID: $advertisingID")
Log.d("LimitAdTracking", "Limit Ad Tracking: $limitAdTracking")
} catch (e: Settings.SettingNotFoundException) {
Log.e("AdvertisingID", "Advertising ID not supported on this device", e)
}
}
}
中文 OEM 识别码
OAID:应为没有 Google Play 服务的中国制造设备提供开放式广告标识符。
开放广告标识符 (OAID)
开放广告标识符(OAID)是用于在中国制造的安卓设备上发布广告的唯一匿名标识符。由移动安全联盟(MSA)推出,用于替代无法使用 Google Play 服务的设备上的 GAID。
支持的设备:华为、小米、OPPO、vivo 和其他中国制造的安卓设备
通过MSA SDK或华为移动服务 (HMS) 访问。
依赖关系
dependencies {
implementation 'com.bun.msa.sdk:msa:1.0.26'
}
执行
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.bun.msa.sdk.DeviceId;
import com.bun.msa.sdk.DeviceIdSupplier;
import com.bun.msa.sdk.IIdentifierListener;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "OAIDExample";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getOAID();
}
private void getOAID() {
try {
DeviceId deviceId = new DeviceId(this);
deviceId.getDeviceIds(new IIdentifierListener() {
@Override
public void onSupport(boolean isSupport, DeviceIdSupplier supplier) {
if (isSupport && supplier != null) {
String oaid = supplier.getOAID();
Log.d(TAG, "OAID: " + oaid);
} else {
Log.e(TAG, "OAID not supported on this device");
}
}
});
} catch (Exception e) {
Log.e(TAG, "Error retrieving OAID", e);
}
}
}
依赖关系
dependencies {
implementation 'com.bun.msa.sdk:msa:1.0.26'
}
实现
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.bun.msa.sdk.DeviceId
import com.bun.msa.sdk.DeviceIdSupplier
import com.bun.msa.sdk.IIdentifierListener
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "OAIDExample"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getOAID()
}
private fun getOAID() {
try {
val deviceId = DeviceId(this)
deviceId.getDeviceIds(object : IIdentifierListener {
override fun onSupport(isSupport: Boolean, supplier: DeviceIdSupplier?) {
if (isSupport && supplier != null) {
val oaid = supplier.oAID
Log.d(TAG, "OAID: $oaid")
} else {
Log.e(TAG, "OAID not supported on this device")
}
}
})
} catch (e: Exception) {
Log.e(TAG, "Error retrieving OAID", e)
}
}
}
安卓 ID 回退
ANDI 限制:只有在没有其他可用标识符且应用程序未通过 Google Play Store 发布的情况下,才能提供 Android ID。 禁止用于 Google Play 应用程序。
Android ID (ANDI)
安卓 ID 是设备首次设置时生成的唯一 64 位标识符。 从安卓 8.0(奥利奥)开始,按应用程序和用户范围设置,不同的应用程序会收到不同的安卓 ID,除非它们共享相同的签名密钥。
持久性:除非设备出厂重置或 OTA 更新后卸载/重新安装应用程序,否则保持不变。
import android.provider.Settings;
import android.content.Context;
String androidId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID
);
import android.provider.Settings
val androidId: String = Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
网络和跨平台标识符
网络应用程序和跨平台实施需要奇异设备 ID (SDID),以便进行准确的归属跟踪。
所需网络标识符
SDID:Web、PC、控制台和 CTV 平台的所有 S2S 请求都需要单一设备 ID。
奇异网络 SDK 设备 ID
奇异设备 ID (SDID) 为网络应用程序和非移动平台提供一致的跨会话跟踪。
前提条件:在检索 SDID 之前,必须先实施并初始化 Singular Web SDK。
使用方法
// Retrieve SDID after Singular SDK initialization
const sdid = window.singularSdk.getSingularDeviceId();
console.log("Singular Device ID:", sdid);
实施注意事项:只有在 Singular SDK 成功初始化后才能调用getSingularDeviceId(),在初始化前尝试检索将返回空。
移动设备参数
所需的设备参数为移动平台上的归因和分析提供了重要的上下文。
所需参数
移动平台:iOS和Android的所有S2S请求都要求提供Locale、Device Make、Device Model和Build。
参数检索
收集地域、制造商、型号和构建信息,以进行完整的设备剖析。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sys/sysctl.h>
// Retrieve Locale
NSString *retrieveLocale() {
NSString *locale = [[NSLocale currentLocale] localeIdentifier];
NSLog(@"Locale: %@", locale);
return locale;
}
// Retrieve Manufacturer (always Apple for iOS)
NSString *retrieveManufacturer() {
return @"Apple";
}
// Retrieve Device Model
NSString *deviceModel() {
size_t bufferSize = 64;
char model[bufferSize];
int status = sysctlbyname("hw.machine", model, &bufferSize, NULL, 0);
if (status == 0) {
NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
NSLog(@"Device Model: %@", deviceModel);
return deviceModel;
} else {
NSLog(@"Unable to retrieve device model.");
return nil;
}
}
// Retrieve Build Version
NSString *buildVersion() {
size_t bufferSize = 64;
char build[bufferSize];
int status = sysctlbyname("kern.osversion", build, &bufferSize, NULL, 0);
if (status == 0) {
NSString *buildVersion = [NSString stringWithCString:build encoding:NSUTF8StringEncoding];
NSLog(@"Build Version: %@", buildVersion);
return buildVersion;
} else {
NSLog(@"Unable to retrieve build version.");
return nil;
}
}
import Foundation
import UIKit
// Retrieve Locale
func retrieveLocale() - String {
let locale = Locale.current.identifier
print("Locale: \(locale)")
return locale
}
// Retrieve Manufacturer (always Apple for iOS)
func retrieveManufacturer() - String {
return "Apple"
}
// Retrieve Device Model
func deviceModel() - String? {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
print("Device Model: \(identifier)")
return identifier
}
// Retrieve Build Version
func buildVersion() - String? {
var size: Int = 0
sysctlbyname("kern.osversion", nil, &size, nil, 0)
var build = [CChar](repeating: 0, count: size)
sysctlbyname("kern.osversion", &build, &size, nil, 0)
let buildVersion = String(cString: build)
print("Build Version: \(buildVersion)")
return buildVersion
}
import android.os.Build;
import java.util.Locale;
// Locale (lc parameter)
String locale = Locale.getDefault().toString();
// Device Make (ma parameter)
String deviceMake = Build.MANUFACTURER;
// Device Model (mo parameter)
String deviceModel = Build.MODEL;
// Build (bd parameter)
String build = "Build/" + Build.ID;
import android.os.Build
import java.util.Locale
// Locale (lc parameter)
val locale: String = Locale.getDefault().toString()
// Device Make (ma parameter)
val deviceMake: String = Build.MANUFACTURER
// Device Model (mo parameter)
val deviceModel: String = Build.MODEL
// Build (bd parameter)
val build: String = "Build/" + Build.ID
参数映射:
- 地域 (lc):语言和地区代码(如 en_US、zh_CN
- 制造商 (ma):设备制造商(苹果、三星、小米
- 型号 (mo):具体设备型号(iPhone14,2、SM-G991B
- 版本 (bd):前缀为 "Build/"的操作系统构建版本