设备数据检索指南
全面介绍如何检索特定平台的设备标识符和参数,这些标识符和参数是准确的 S2S API 归因和广告系列测量所必需的。
必需的设备标识符: Singular 要求在所有 API 请求中包含特定的设备标识符,以实现准确归因。
移动平台:
- Android (Google Play): Google Advertising ID (GAID/AIFA) 和 App Set ID (ASID)
- Android (Amazon): 适用于 Fire 设备的 Amazon Advertising ID (AMID)
- Android(中国 OEM 厂商): 适用于没有 Google Play Services 的设备的 Open Advertising ID (OAID)
- Android(后备方案): 仅当没有其他可用标识符时使用的 Android ID (ANDI)
- iOS: Identifier for Vendors (IDFV) 以及 Identifier for Advertisers (IDFA)(可用时)
设备参数: 移动平台需要区域设置、设备制造商、设备型号和构建版本
下面的代码示例演示了每个平台和标识符类型的检索方法。
示例应用
参考实现
适用于 iOS 和 Android 的完整可运行示例,演示了设备数据检索模式。
Singular Device ID (SDID)
如果你的应用包含 Singular SDK(混合集成),请从 SDK 检索 Singular Device ID (SDID) 并将其作为
sdid
参数发送,而不是收集特定平台的标识符。SDK 会自动生成并解析 SDID。有关如何在各个 SDK 中检索 SDID,请参阅 SDK 集成指南:
对于设备上没有 SDK 的纯服务器到服务器集成,请在客户端生成一个 UUIDv4 作为 SDID,或继续使用下面记录的特定平台标识符。
iOS 设备标识符
iOS 设备需要 IDFV(始终)和 IDFA(当用户授予跟踪权限时),以及 ATT 授权状态,以实现准确归因。
必需的 iOS 标识符
标识符要求:
- IDFV: 无论跟踪权限如何,所有 S2S 请求都需要
- IDFA: 如果用户授予了 App Tracking Transparency 同意,则应提供
- ATT 状态: 所有请求都需要授权状态码(0-3)
实现指南
Identifier for Advertisers (IDFA)
Identifier for Advertisers (IDFA) 使广告主能够将用户行为(广告点击、应用安装)跟踪并归因到特定广告系列,以实现精准定向和优化。
从 iOS 14.5 开始,用户必须通过 App Tracking Transparency (ATT) 框架选择加入,应用才能访问 IDFA。没有用户同意时,IDFA 返回全零,从而限制跟踪能力。
Identifier for Vendors (IDFV)
Identifier for Vendors (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=已授权
Android 设备标识符 (Google Play)
带有 Google Play Services 的 Android 设备在所有请求中都需要 App Set ID (ASID),并在可用时提供 Google Advertising ID (GAID/AIFA)。
必需的 Google Play 标识符
标识符要求:
- ASID: Google Play 设备的所有 S2S 请求都需要
- AIFA/GAID: 可用时应提供(未选择退出)
实现指南
Google Advertising Identifier (GAID)
Google Advertising Identifier (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
}
}
}
}
App Set ID (ASID)
Android App Set 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)
}
}
}
Android 设备标识符(非 Google Play)
没有 Google Play Services 的 Android 设备需要根据设备制造商和分发方式使用替代标识符。
Amazon 设备标识符
AMID: 对于没有 Google Play Services 的 Amazon Fire 设备,应提供 Amazon Advertising ID。
Amazon ID (AMID)
Amazon Advertising Identifier 在没有 Google Play Services 的 Amazon Fire 设备上支持用户可重置的广告跟踪,在保护用户隐私的同时实现归因。
要求:
- 适用于运行 Fire OS 5.1+ 的 Amazon Fire 设备
- 尊重用户的 Limit Ad Tracking 偏好设置
- 在非 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 Services 的中国制造设备,应提供 Open Advertising Identifier。
Open Advertising ID (OAID)
Open Advertising Identifier (OAID) 是在中国制造的 Android 设备上用于广告的唯一、匿名标识符。它由移动安全联盟 (MSA) 推出,作为在 Google Play Services 不可用的设备上替代 GAID 的方案。
支持的设备: 华为、小米、OPPO、Vivo 以及其他中国制造的 Android 设备
通过 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)
}
}
}
Android ID 后备方案
ANDI 限制: 仅当没有其他可用标识符且应用未通过 Google Play Store 分发时,才可以提供 Android ID。Google Play 应用禁止使用。
Android ID (ANDI)
Android ID 是设备首次设置时生成的唯一 64 位标识符。从 Android 8.0 (Oreo) 开始,它按应用和按用户划分范围——不同的应用会收到不同的 Android 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
)
Web 和跨平台标识符
Web 应用和跨平台实现需要 Singular Device ID (SDID) 来进行准确的归因跟踪。
必需的 Web 标识符
SDID: Web、PC、主机和 CTV 平台的所有 S2S 请求都需要 Singular Device ID。
Singular Web SDK Device ID
Singular Device ID (SDID) 为 Web 应用和非移动平台提供一致的跨会话跟踪。
前提条件: 在检索 SDID 之前,必须先实现并初始化 Singular Web SDK。
用法
// Retrieve SDID after Singular SDK initialization
const sdid = window.singularSdk.getSingularDeviceId();
console.log("Singular Device ID:", sdid);
实现说明:
仅在 Singular SDK 成功初始化后才调用
getSingularDeviceId()
——在初始化之前尝试检索会返回 null。
移动设备参数
必需的设备参数为移动平台上的归因和分析提供了重要的上下文信息。
必需参数
移动平台: iOS 和 Android 的所有 S2S 请求都需要区域设置、设备制造商、设备型号和构建版本。
参数检索
收集区域设置、制造商、型号和构建信息,以获得完整的设备画像。
#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): 设备制造商(Apple、Samsung、Xiaomi)
- 型号 (mo): 具体设备型号(iPhone14,2、SM-G991B)
- 构建版本 (bd): 以 "Build/" 为前缀的操作系统构建版本
时间戳参数
可选的时间戳参数描述了设备的安装和更新状态。这些是操作系统报告的值,在设备上收集并随 SESSION 请求发送。
install_time 检索
install_time
是操作系统报告的应用实际安装到设备上的 Unix 时间戳(秒)。在 Android 上,读取
PackageInfo.firstInstallTime
(毫秒)并除以 1000。iOS 没有公开安装时间 API,因此使用应用 Documents 目录的创建日期作为标准近似值。
#import <Foundation/Foundation.h>
// install_time: creation date of the Documents directory (proxy)
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *docs = [[fm URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] firstObject];
NSDictionary *attrs = [fm attributesOfItemAtPath:docs.path error:nil];
NSDate *created = attrs[NSFileCreationDate];
long installTime = (long)[created timeIntervalSince1970];
NSLog(@"install_time: %ld", installTime);
import Foundation
// install_time: creation date of the Documents directory (proxy)
let docs = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask).first!
let attrs = try? FileManager.default.attributesOfItem(atPath: docs.path)
let created = attrs?[.creationDate] as? Date
let installTime = created.map { Int($0.timeIntervalSince1970) }
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
// install_time: PackageInfo.firstInstallTime is in milliseconds
long installTime = 0L;
try {
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
installTime = info.firstInstallTime / 1000L; // seconds
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
import android.content.pm.PackageManager
// install_time: PackageInfo.firstInstallTime is in milliseconds
val installMillis = packageManager
.getPackageInfo(packageName, 0)
.firstInstallTime
val installTime = installMillis / 1000 // seconds
这是设备安装时间,而非 Singular 的归因安装时间。归因使用首次会话(
install=true
)的会话时间戳(
utime
)。
update_time 检索
update_time
是操作系统报告的应用在设备上最后一次更新的 Unix 时间戳(秒)。在 Android 上,读取
PackageInfo.lastUpdateTime
(毫秒)并除以 1000。iOS 没有公开更新时间 API,因此使用应用包可执行文件的创建日期作为标准近似值,该日期在每次更新时都会被重写。
#import <Foundation/Foundation.h>
// update_time: creation date of the app bundle executable (proxy)
NSString *execPath = [[NSBundle mainBundle] executablePath];
NSDictionary *attrs = [[NSFileManager defaultManager]
attributesOfItemAtPath:execPath error:nil];
NSDate *updated = attrs[NSFileCreationDate];
long updateTime = (long)[updated timeIntervalSince1970];
NSLog(@"update_time: %ld", updateTime);
import Foundation
// update_time: creation date of the app bundle executable (proxy)
let execPath = Bundle.main.executablePath!
let attrs = try? FileManager.default.attributesOfItem(atPath: execPath)
let updated = attrs?[.creationDate] as? Date
let updateTime = updated.map { Int($0.timeIntervalSince1970) }
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
// update_time: PackageInfo.lastUpdateTime is in milliseconds
long updateTime = 0L;
try {
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
updateTime = info.lastUpdateTime / 1000L; // seconds
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
import android.content.pm.PackageManager
// update_time: PackageInfo.lastUpdateTime is in milliseconds
val updateMillis = packageManager
.getPackageInfo(packageName, 0)
.lastUpdateTime
val updateTime = updateMillis / 1000 // seconds
在应用从未更新过的设备上,此值等于
install_time
。它反映的是应用更新,而非归因活动。