115 lines
3.9 KiB
Dart
115 lines
3.9 KiB
Dart
import 'dart:async';
|
||
import 'dart:convert';
|
||
|
||
import 'package:adjust_sdk/adjust_attribution.dart';
|
||
import 'package:adjust_sdk/adjust.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:play_install_referrer/play_install_referrer.dart';
|
||
|
||
/// 归因信息服务(优先从 Adjust 获取,fallback 使用 Play Install Referrer)
|
||
class ReferrerService {
|
||
ReferrerService._();
|
||
|
||
static String? _cachedReferrer;
|
||
static String _referrerSource = 'gg';
|
||
static final Completer<String?> _completer = Completer<String?>();
|
||
|
||
/// attribution 回调的 Completer,与 getAttributionWithTimeout 竞速,谁先返回用谁
|
||
static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
|
||
Completer<AdjustAttribution?>();
|
||
|
||
static const int _adjustTimeoutMs = 15000;
|
||
|
||
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
||
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
||
if (!_attributionCallbackCompleter.isCompleted) {
|
||
_attributionCallbackCompleter.complete(attribution);
|
||
}
|
||
}
|
||
|
||
/// 归因来源:Adjust 为 android_adjust,Play Install Referrer 为 gg
|
||
static String get referrerSource => _referrerSource;
|
||
|
||
/// JSON 可编码的 costAmount:保留字段,非有限数字用字符串避免 encode 抛错
|
||
static Object? _jsonEncodableCostAmount(num? v) {
|
||
if (v == null) return null;
|
||
if (v is double && !v.isFinite) return v.toString();
|
||
return v;
|
||
}
|
||
|
||
/// 将完整 AdjustAttribution 序列化为 JSON(供 digest / Base64),不省略任何键
|
||
static String _attributionToDigest(AdjustAttribution attr) {
|
||
final map = <String, dynamic>{
|
||
'trackerToken': attr.trackerToken,
|
||
'trackerName': attr.trackerName,
|
||
'network': attr.network,
|
||
'campaign': attr.campaign,
|
||
'adgroup': attr.adgroup,
|
||
'creative': attr.creative,
|
||
'clickLabel': attr.clickLabel,
|
||
'costType': attr.costType,
|
||
'costAmount': _jsonEncodableCostAmount(attr.costAmount),
|
||
'costCurrency': attr.costCurrency,
|
||
'jsonResponse': attr.jsonResponse,
|
||
'fbInstallReferrer': attr.fbInstallReferrer,
|
||
};
|
||
try {
|
||
return jsonEncode(map);
|
||
} catch (_) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/// 获取 referrer/digest,优先从 Adjust 归因获取,fallback 使用 Play Install Referrer
|
||
/// 同时监听 attributionCallback,首次安装时归因往往通过回调返回(可能晚于 getAttributionWithTimeout)
|
||
static Future<String?> getReferrer() async {
|
||
if (_cachedReferrer != null) return _cachedReferrer;
|
||
if (_completer.isCompleted) return _completer.future;
|
||
|
||
var digest = '';
|
||
try {
|
||
// 竞速:getAttributionWithTimeout 与 attribution 回调,谁先返回用谁
|
||
final attribution = await Future.any<AdjustAttribution?>([
|
||
(() async {
|
||
try {
|
||
return await Adjust.getAttributionWithTimeout(_adjustTimeoutMs);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
})(),
|
||
_attributionCallbackCompleter.future,
|
||
]);
|
||
if (attribution != null) {
|
||
final raw = _attributionToDigest(attribution);
|
||
if (raw.isNotEmpty) {
|
||
_referrerSource = 'android_adjust';
|
||
digest = base64Encode(utf8.encode(raw));
|
||
}
|
||
}
|
||
} catch (_) {
|
||
digest = '';
|
||
}
|
||
|
||
if (digest.isEmpty) {
|
||
_referrerSource = 'gg';
|
||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||
try {
|
||
final details = await PlayInstallReferrer.installReferrer;
|
||
digest = details.installReferrer ?? '';
|
||
} catch (_) {
|
||
digest = '';
|
||
}
|
||
}
|
||
}
|
||
|
||
_cachedReferrer = digest;
|
||
if (!_completer.isCompleted) _completer.complete(_cachedReferrer);
|
||
return _cachedReferrer;
|
||
}
|
||
|
||
/// 初始化并缓存 referrer(建议在 app 启动时调用)
|
||
static Future<void> init() async {
|
||
await getReferrer();
|
||
}
|
||
}
|