优化:启动速度优化
This commit is contained in:
parent
42d9cf3fd2
commit
79d4e32e3e
@ -13,7 +13,7 @@
|
|||||||
<application
|
<application
|
||||||
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
||||||
android:label="PetsHero AI"
|
android:label="PetsHero AI"
|
||||||
android:name="${applicationName}"
|
android:name=".PetsHeroApplication"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@ -8,9 +8,8 @@ import io.flutter.embedding.android.FlutterActivity
|
|||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// 必须在 super.onCreate 之前初始化,否则 facebook_app_events 插件注册时会因 SDK 未初始化而崩溃
|
super.onCreate(savedInstanceState)
|
||||||
FacebookSdk.sdkInitialize(applicationContext)
|
// Facebook SDK 调试日志(SDK 已在 [PetsHeroApplication] 中初始化)
|
||||||
// Facebook SDK 调试日志:需 buildConfigField FACEBOOK_DEBUG_LOGS=true
|
|
||||||
if (BuildConfig.FACEBOOK_DEBUG_LOGS) {
|
if (BuildConfig.FACEBOOK_DEBUG_LOGS) {
|
||||||
try {
|
try {
|
||||||
FacebookSdk.setIsDebugEnabled(true)
|
FacebookSdk.setIsDebugEnabled(true)
|
||||||
@ -19,6 +18,5 @@ class MainActivity : FlutterActivity() {
|
|||||||
FacebookSdk.addLoggingBehavior(LoggingBehavior.DEVELOPER_ERRORS)
|
FacebookSdk.addLoggingBehavior(LoggingBehavior.DEVELOPER_ERRORS)
|
||||||
} catch (_: Exception) { /* ignore */ }
|
} catch (_: Exception) { /* ignore */ }
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.petsheroai.app
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.facebook.FacebookSdk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Facebook SDK 初始化从 [MainActivity] 挪到 Application,避免阻塞 Activity.onCreate,
|
||||||
|
* 让 Flutter 引擎与首帧更早开始(减轻「一直停在原生启动屏」的概率)。
|
||||||
|
*/
|
||||||
|
class PetsHeroApplication : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
FacebookSdk.sdkInitialize(applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
90
lib/app.dart
90
lib/app.dart
@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:facebook_app_events/facebook_app_events.dart';
|
import 'package:facebook_app_events/facebook_app_events.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
|
|
||||||
import 'core/auth/auth_service.dart';
|
import 'core/auth/auth_service.dart';
|
||||||
import 'core/nav/app_route_observer.dart';
|
import 'core/nav/app_route_observer.dart';
|
||||||
@ -41,6 +44,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
// 冷启动时进程已在 resumed,didChangeAppLifecycleState 往往收不到「变为 resumed」,需首帧后再手动打一次
|
// 冷启动时进程已在 resumed,didChangeAppLifecycleState 往往收不到「变为 resumed」,需首帧后再手动打一次
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// 去掉原生启动屏(黑底+Logo),进入 Flutter;之后由登录遮罩承接等待提示
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
_reportFacebookActivateApp('first_frame');
|
_reportFacebookActivateApp('first_frame');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -96,18 +101,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return const _StartupLoginOverlay();
|
||||||
child: AbsorbPointer(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withValues(alpha: 0.2),
|
|
||||||
child: const Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: AppColors.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -144,6 +138,78 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 启动阶段等待 [AuthService.loginComplete]:久等时提示网络/服务端问题,避免用户误以为「死机」。
|
||||||
|
class _StartupLoginOverlay extends StatefulWidget {
|
||||||
|
const _StartupLoginOverlay();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_StartupLoginOverlay> createState() => _StartupLoginOverlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StartupLoginOverlayState extends State<_StartupLoginOverlay> {
|
||||||
|
static const _longWaitAfter = Duration(seconds: 22);
|
||||||
|
Timer? _longWaitTimer;
|
||||||
|
bool _showNetworkHint = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_longWaitTimer = Timer(_longWaitAfter, () {
|
||||||
|
if (mounted) setState(() => _showNetworkHint = true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_longWaitTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned.fill(
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withValues(alpha: 0.22),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(color: AppColors.primary),
|
||||||
|
if (_showNetworkHint) ...[
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
Text(
|
||||||
|
'网络较慢或暂时无法连接',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.surface.withValues(alpha: 0.96),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'请检查 Wi‑Fi 或移动数据,稍候片刻。若网络正常,可能是服务器繁忙,请过一会再试。',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.surface.withValues(alpha: 0.78),
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _MainScaffold extends StatefulWidget {
|
class _MainScaffold extends StatefulWidget {
|
||||||
const _MainScaffold({
|
const _MainScaffold({
|
||||||
required this.currentTab,
|
required this.currentTab,
|
||||||
|
|||||||
@ -37,4 +37,7 @@ abstract final class ApiConfig {
|
|||||||
|
|
||||||
/// 代理入口完整 URL
|
/// 代理入口完整 URL
|
||||||
static String get proxyUrl => '$baseUrl$proxyPath';
|
static String get proxyUrl => '$baseUrl$proxyPath';
|
||||||
|
|
||||||
|
/// 单次 HTTP 请求超时(避免弱网/服务端无响应时永久卡住启动流程)
|
||||||
|
static const Duration httpRequestTimeout = Duration(seconds: 20);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -282,11 +283,22 @@ class ProxyClient {
|
|||||||
|
|
||||||
final url = '$_baseUrl${ApiConfig.proxyPath}';
|
final url = '$_baseUrl${ApiConfig.proxyPath}';
|
||||||
_log('真实请求URL: $url');
|
_log('真实请求URL: $url');
|
||||||
final response = await http.post(
|
http.Response response;
|
||||||
Uri.parse(url),
|
try {
|
||||||
headers: {'Content-Type': 'application/json'},
|
response = await http
|
||||||
body: jsonEncode(proxyBody),
|
.post(
|
||||||
);
|
Uri.parse(url),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode(proxyBody),
|
||||||
|
)
|
||||||
|
.timeout(ApiConfig.httpRequestTimeout);
|
||||||
|
} on TimeoutException {
|
||||||
|
_proxyLog.d('HTTP 超时 (${ApiConfig.httpRequestTimeout.inSeconds}s): $path');
|
||||||
|
return ApiResponse(
|
||||||
|
code: -1,
|
||||||
|
msg: 'Request timed out. Check your network and try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return _parseResponse(response);
|
return _parseResponse(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -290,12 +290,8 @@ class AuthService {
|
|||||||
UserState.setNavigate(countryCode);
|
UserState.setNavigate(countryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 归因:android_adjust、gg 各上报一次(均等待响应);再拉一次 common_info,extConfig 影响首页时重载分类/任务
|
// 3. 归因与 common_info:后台执行,不阻塞 loginComplete(避免服务端慢/挂起导致一直卡在启动遮罩)
|
||||||
try {
|
unawaited(_runPostLoginReferrerWork(uid!, deviceId));
|
||||||
await _reportBothReferrersAndRefreshCommonInfo(uid!, deviceId);
|
|
||||||
} catch (e) {
|
|
||||||
_logMsg('referrer/common_info 流程异常: $e');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_logMsg('init: 登录失败');
|
_logMsg('init: 登录失败');
|
||||||
}
|
}
|
||||||
@ -371,4 +367,12 @@ class AuthService {
|
|||||||
'common_info 失败: code=${commonRes.code} msg=${commonRes.msg}');
|
'common_info 失败: code=${commonRes.code} msg=${commonRes.msg}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> _runPostLoginReferrerWork(String uid, String deviceId) async {
|
||||||
|
try {
|
||||||
|
await _reportBothReferrersAndRefreshCommonInfo(uid, deviceId);
|
||||||
|
} catch (e, _) {
|
||||||
|
_logMsg('referrer/common_info 后台任务失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,12 @@ class ReferrerService {
|
|||||||
/// 过长会拖慢 [AuthService.init] 内首次 getReferrer;过短可能拿不到 Adjust,仍有 Play Install Referrer fallback
|
/// 过长会拖慢 [AuthService.init] 内首次 getReferrer;过短可能拿不到 Adjust,仍有 Play Install Referrer fallback
|
||||||
static const int _adjustTimeoutMs = 4500;
|
static const int _adjustTimeoutMs = 4500;
|
||||||
|
|
||||||
|
/// 与 [Future.any] 外层兜底,避免回调 Future 永不完成时永远挂起
|
||||||
|
static const Duration _adjustRaceTimeout = Duration(seconds: 6);
|
||||||
|
|
||||||
|
/// Play Install Referrer 读取超时(部分机型/系统上可能极慢)
|
||||||
|
static const Duration _playInstallReferrerTimeout = Duration(seconds: 10);
|
||||||
|
|
||||||
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
||||||
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
||||||
if (!_attributionCallbackCompleter.isCompleted) {
|
if (!_attributionCallbackCompleter.isCompleted) {
|
||||||
@ -79,7 +85,7 @@ class ReferrerService {
|
|||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
_attributionCallbackCompleter.future,
|
_attributionCallbackCompleter.future,
|
||||||
]);
|
]).timeout(_adjustRaceTimeout, onTimeout: () => null);
|
||||||
if (attribution != null) {
|
if (attribution != null) {
|
||||||
final raw = _attributionToDigest(attribution);
|
final raw = _attributionToDigest(attribution);
|
||||||
if (raw.isNotEmpty) {
|
if (raw.isNotEmpty) {
|
||||||
@ -95,7 +101,8 @@ class ReferrerService {
|
|||||||
_referrerSource = 'gg';
|
_referrerSource = 'gg';
|
||||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||||
try {
|
try {
|
||||||
final details = await PlayInstallReferrer.installReferrer;
|
final details = await PlayInstallReferrer.installReferrer
|
||||||
|
.timeout(_playInstallReferrerTimeout);
|
||||||
digest = details.installReferrer ?? '';
|
digest = details.installReferrer ?? '';
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
digest = '';
|
digest = '';
|
||||||
@ -129,7 +136,8 @@ class ReferrerService {
|
|||||||
static Future<String> getGgReferrerDigest() async {
|
static Future<String> getGgReferrerDigest() async {
|
||||||
if (defaultTargetPlatform != TargetPlatform.android) return '';
|
if (defaultTargetPlatform != TargetPlatform.android) return '';
|
||||||
try {
|
try {
|
||||||
final details = await PlayInstallReferrer.installReferrer;
|
final details = await PlayInstallReferrer.installReferrer
|
||||||
|
.timeout(_playInstallReferrerTimeout);
|
||||||
return details.installReferrer ?? '';
|
return details.installReferrer ?? '';
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@ -24,9 +24,9 @@ void main() async {
|
|||||||
statusBarBrightness: Brightness.light,
|
statusBarBrightness: Brightness.light,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Adjust 初始化后执行登录,确保登录时归因数据已就绪
|
// 先进入 Flutter 首帧,尽快结束原生启动屏;登录与内购监听紧随其后
|
||||||
AuthService.init();
|
|
||||||
runApp(const App());
|
runApp(const App());
|
||||||
|
AuthService.init();
|
||||||
// 尽早订阅 purchaseStream,否则未确认订单不会出现在 queryPastPurchases 中,补单会为空
|
// 尽早订阅 purchaseStream,否则未确认订单不会出现在 queryPastPurchases 中,补单会为空
|
||||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||||
GooglePlayPurchaseService.startPendingPurchaseListener();
|
GooglePlayPurchaseService.startPendingPurchaseListener();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user