优化:Facebook统计问题,设备ID获取bug

This commit is contained in:
ivan 2026-03-29 14:07:57 +08:00
parent 4e90e6f030
commit 4ef714453e
11 changed files with 165 additions and 38 deletions

View File

@ -59,6 +59,11 @@ android {
targetSdk 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// Flutter arm + x86_64 libflutter.so 32 x86 x86 32 .so
// Intel/AMD **x86_64** x86
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
// Facebook SDK true线 false
buildConfigField "boolean", "FACEBOOK_DEBUG_LOGS", (localProperties.getProperty("facebook.debug") ?: "true")
}

View File

@ -34,6 +34,12 @@
<meta-data
android:name="com.facebook.sdk.ClientToken"
android:value="@string/facebook_client_token" />
<!-- 关闭自动 App 事件(含隐式内购 fb_mobile_purchase避免与 Dart 内 logPurchase 重复计数。
会话与显式埋点仍由应用代码负责(如 main.dart activateApp、AdjustEvents.trackPurchaseSuccess
若开发者后台「自动记录应用事件」与本地冲突,以控制台设置为准。 -->
<meta-data
android:name="com.facebook.sdk.AutoLogAppEventsEnabled"
android:value="false" />
<!-- Facebook SDK 调试Codeless 事件调试时启用 -->
<meta-data
android:name="com.facebook.sdk.CodelessDebugLogEnabled"

View File

@ -30,6 +30,9 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<!-- 关闭 Facebook SDK 自动 App 事件(含隐式购买),与 Android AutoLogAppEventsEnabled=false 对齐 -->
<key>FacebookAutoLogAppEventsEnabled</key>
<false/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>

View File

@ -1,6 +1,9 @@
import 'package:facebook_app_events/facebook_app_events.dart';
import 'package:flutter/material.dart';
import 'core/auth/auth_service.dart';
import 'core/config/facebook_config.dart';
import 'core/log/app_logger.dart';
import 'core/theme/app_colors.dart';
import 'core/theme/app_theme.dart';
import 'core/user/user_state.dart';
@ -24,9 +27,45 @@ class App extends StatefulWidget {
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
class _AppState extends State<App> with WidgetsBindingObserver {
NavTab _currentTab = NavTab.home;
static final _fbLog = AppLogger('FB');
final FacebookAppEvents _fbAppEvents = FacebookAppEvents();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// resumeddidChangeAppLifecycleState resumed
WidgetsBinding.instance.addPostFrameCallback((_) {
_reportFacebookActivateApp('first_frame');
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
/// `AutoLogAppEventsEnabled=false` Facebook ** + **activateApp
void _reportFacebookActivateApp(String reason) {
_fbAppEvents.activateApp().then((_) {
if (FacebookConfig.debugLogs) {
_fbLog.d('activateApp手动: $reason');
}
});
}
/// initState addPostFrameCallback
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_reportFacebookActivateApp('lifecycle_resumed');
}
}
@override
Widget build(BuildContext context) {
return UserCreditsScope(

View File

@ -95,7 +95,7 @@ abstract final class AdjustEvents {
_trackFb('payment_failed', () => _fb.logEvent(name: 'payment_failed'));
}
/// fast_login
/// fast_login equip=true / firstRegister
static void trackRegister() {
_track(register);
_trackFb('CompletedRegistration',

View File

@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:android_id/android_id.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
@ -10,6 +12,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../adjust/adjust_events.dart';
import '../api/api_client.dart';
import 'auth_token_store.dart';
import '../api/api_config.dart';
import '../api/proxy_client.dart';
import '../api/services/user_api.dart';
@ -56,19 +59,43 @@ class AuthService {
_log.d(msg);
}
/// IDAndroid: androidId, iOS: identifierForVendor, Web: fallback
static const _prefsKeyFallbackDeviceId = 'persisted_device_id';
/// fast_login origin
///
/// - **Android**`Settings.Secure.ANDROID_ID`[`android_id`](https://pub.dev/packages/android_id)\
/// `device_info_plus` `AndroidDeviceInfo.id` **`Build.ID`ROM ****** ID
/// - **iOS**`identifierForVendor`
/// - ** / ** SharedPreferences id
static Future<String> _getDeviceId() async {
final deviceInfo = DeviceInfoPlugin();
switch (defaultTargetPlatform) {
case TargetPlatform.android:
final android = await deviceInfo.androidInfo;
return android.id;
case TargetPlatform.iOS:
final ios = await deviceInfo.iosInfo;
return ios.identifierForVendor ?? 'ios-unknown';
default:
return 'device-${DateTime.now().millisecondsSinceEpoch}';
final androidId = await const AndroidId().getId();
if (androidId != null && androidId.isNotEmpty) {
return androidId;
}
_logMsg('_getDeviceId: ANDROID_ID 为空,使用本地持久化 fallback');
return _persistedFallbackDeviceId();
case TargetPlatform.iOS:
final ios = await DeviceInfoPlugin().iosInfo;
final idfv = ios.identifierForVendor;
if (idfv != null && idfv.isNotEmpty) return idfv;
return _persistedFallbackDeviceId();
default:
return _persistedFallbackDeviceId();
}
}
static Future<String> _persistedFallbackDeviceId() async {
final prefs = await SharedPreferences.getInstance();
var id = prefs.getString(_prefsKeyFallbackDeviceId);
if (id != null && id.isNotEmpty) return id;
final random = Random.secure();
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
id = base64UrlEncode(bytes).replaceAll('=', '');
await prefs.setString(_prefsKeyFallbackDeviceId, id);
_logMsg('_getDeviceId: 已生成并持久化 fallback id');
return id;
}
/// signMD5(deviceId) 32
@ -163,6 +190,12 @@ class AuthService {
_logMsg('init: crest(referrer)=$crest');
}
await AuthTokenStore.restoreToApiClient();
if (ApiClient.instance.proxy.userToken != null &&
ApiClient.instance.proxy.userToken!.isNotEmpty) {
_logMsg('init: 已用本地 token 注入请求头,本次 fast_login 将携带 knight');
}
ApiResponse? res;
for (var i = 0; i < maxRetries; i++) {
if (i > 0) {
@ -193,20 +226,25 @@ class AuthService {
final token = data?['reevaluate'] as String?;
if (token != null && token.isNotEmpty) {
ApiClient.instance.setUserToken(token);
_logMsg('init: 已设置 userToken');
await AuthTokenStore.write(token);
_logMsg('init: 已设置 userToken 并写入本地');
} else {
_logMsg('init: 响应中无 reevaluate (userToken)');
_logMsg('init: 响应中无 reevaluate (userToken),保留原本地 token若有');
}
final prefs = await SharedPreferences.getInstance();
final hadLoggedIn = prefs.getBool('adjust_has_logged_in') ?? false;
if (!hadLoggedIn) {
// equip = firstRegister docs/user_login.md true/1
final equipRaw = data?['equip'];
final equipFirstRegister = equipRaw == true ||
equipRaw == 1 ||
equipRaw == '1' ||
(equipRaw is String && equipRaw.toLowerCase() == 'true');
if (equipFirstRegister) {
AdjustEvents.trackRegister();
await prefs.setBool('adjust_has_logged_in', true);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'adjust_register_date',
DateTime.now().toIso8601String().substring(0, 10),
);
_logMsg('init: 首次登录,已上报 register');
_logMsg('init: equip=true 首次注册,已上报 register');
}
final credits = data?['reveal'] as int?;
if (credits != null) {

View File

@ -0,0 +1,31 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../api/api_client.dart';
/// Token `knight` fast_login `reevaluate`
abstract final class AuthTokenStore {
static const _prefsKey = 'user_knight_token';
static Future<String?> read() async {
final p = await SharedPreferences.getInstance();
return p.getString(_prefsKey);
}
static Future<void> write(String token) async {
final p = await SharedPreferences.getInstance();
await p.setString(_prefsKey, token);
}
static Future<void> clear() async {
final p = await SharedPreferences.getInstance();
await p.remove(_prefsKey);
}
/// token [ApiClient]`fast_login` `knight`
static Future<void> restoreToApiClient() async {
final t = await read();
if (t != null && t.isNotEmpty) {
ApiClient.instance.setUserToken(t);
}
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../core/api/api_client.dart';
import '../../core/auth/auth_token_store.dart';
import '../../core/api/api_config.dart';
import '../../core/api/services/user_api.dart';
import '../../core/user/account_refresh.dart';
@ -408,7 +409,9 @@ class _DeleteAccountDialogState extends State<_DeleteAccountDialog> {
UserState.setAvatar(null);
UserState.setUserName(null);
UserState.setNavigate(null);
await AuthTokenStore.clear();
ApiClient.instance.setUserToken(null);
if (!mounted) return;
Navigator.of(context).pop();
if (widget.parentContext.mounted) {
ScaffoldMessenger.of(widget.parentContext).showSnackBar(

View File

@ -266,6 +266,8 @@ class _RechargeScreenState extends State<RechargeScreen>
return;
}
final purchaseAmount =
(AdjustEvents.parsePrice(item.actualAmount) ?? 0).toDouble();
final payUrl = data?['convert']?.toString();
if (payUrl != null && payUrl.isNotEmpty) {
if (mounted) {
@ -276,21 +278,28 @@ class _RechargeScreenState extends State<RechargeScreen>
),
);
if (mounted && orderId != null && orderId.isNotEmpty) {
_startOrderPolling(orderId: orderId, userId: userId);
_startOrderPolling(
orderId: orderId,
userId: userId,
purchaseAmount: purchaseAmount,
);
}
_showSnackBar(
context, 'Order created. Complete payment in the page.');
AdjustEvents.trackPurchaseSuccess(
(AdjustEvents.parsePrice(item.actualAmount) ?? 0).toDouble());
}
} else {
if (mounted) {
setState(() => _loadingProductId = null);
_showSnackBar(
context, 'Order created. Awaiting payment confirmation.');
if (orderId != null && orderId.isNotEmpty) {
_startOrderPolling(
orderId: orderId,
userId: userId,
purchaseAmount: purchaseAmount,
);
}
}
AdjustEvents.trackPurchaseSuccess(
(AdjustEvents.parsePrice(item.actualAmount) ?? 0).toDouble());
}
} catch (e) {
if (mounted) {
@ -304,8 +313,12 @@ class _RechargeScreenState extends State<RechargeScreen>
}
}
/// webview 1/3/7/15/31/63 status SUCCESS|FAILED|CANCELED SUCCESS
void _startOrderPolling({required String orderId, required String userId}) {
/// webview 1/3/7/15/31/63 status SUCCESS|FAILED|CANCELED SUCCESS Adjust/Facebook
void _startOrderPolling({
required String orderId,
required String userId,
required double purchaseAmount,
}) {
const delays = [1, 2, 4, 8, 16, 32]; // 1,3,7,15,31,63
Future<void> poll(int index) async {
if (index >= delays.length) return;
@ -326,6 +339,7 @@ class _RechargeScreenState extends State<RechargeScreen>
RechargeScreen._log.d('订单轮询 orderId=$orderId status=$status');
if (status == 'SUCCESS' || status == 'FAILED' || status == 'CANCELED') {
if (status == 'SUCCESS') {
await AdjustEvents.trackPurchaseSuccess(purchaseAmount);
refreshAccount();
}
return;

View File

@ -1,7 +1,6 @@
import 'package:adjust_sdk/adjust_attribution.dart';
import 'package:adjust_sdk/adjust_config.dart';
import 'package:adjust_sdk/adjust.dart';
import 'package:facebook_app_events/facebook_app_events.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -9,7 +8,6 @@ import 'package:flutter/services.dart';
import 'app.dart';
import 'core/api/api_config.dart';
import 'core/auth/auth_service.dart';
import 'core/config/facebook_config.dart';
import 'core/log/app_logger.dart';
import 'core/referrer/referrer_service.dart';
import 'core/theme/app_colors.dart';
@ -18,7 +16,7 @@ import 'features/recharge/google_play_purchase_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
_initAdjust();
_initFacebookAppEvents();
// Facebook/ App activateApp app.dart AutoLog
// Adjust ReferrerService Adjust.getAttributionWithTimeout
await ReferrerService.init();
SystemChrome.setSystemUIOverlayStyle(
@ -46,7 +44,6 @@ void _initAdjust() {
appToken,
kDebugMode ? AdjustEnvironment.sandbox : AdjustEnvironment.production,
);
// config.fbAppId = FacebookConfig.appId;
if (kDebugMode || ApiConfig.debugLogs) {
config.logLevel = AdjustLogLevel.verbose;
}
@ -54,16 +51,6 @@ void _initAdjust() {
Adjust.initSdk(config);
}
final _fbAppEvents = FacebookAppEvents();
void _initFacebookAppEvents() {
// activateAppFacebook 广
_fbAppEvents.activateApp();
if (FacebookConfig.debugLogs) {
AppLogger('FB').d('activateApp 已上报');
}
}
final _adjustLog = AppLogger('Adjust');
void _onAdjustAttribution(AdjustAttribution attribution) {

View File

@ -1,7 +1,7 @@
name: pets_hero_ai
description: PetsHero AI Application.
publish_to: 'none'
version: 1.1.11+22
version: 1.1.13+24
environment:
sdk: '>=3.0.0 <4.0.0'
@ -34,6 +34,7 @@ dependencies:
webview_flutter: ^4.10.0
screen_secure: ^1.0.3
flutter_native_splash: ^2.4.7
android_id: ^0.5.1
dev_dependencies:
flutter_test: