From 6c23b545fdb0dbc00b8b36f597e8bcb5ae41ffb3 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Sat, 1 Aug 2026 23:47:14 +0800 Subject: [PATCH] fix: 修复友盟隐私合规初始化时机 --- android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt | 40 ++++++++++++++++++++++++++++++---------- lib/app/splash_page.dart | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------- lib/common/core/app_config_helper.dart | 18 +++++++----------- lib/main.dart | 20 ++++++-------------- lib/utils/sp_util.dart | 12 ++++++------ pubspec.yaml | 2 +- 6 files changed, 203 insertions(+), 115 deletions(-) diff --git a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt index e88da04..88c9499 100644 --- a/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt +++ b/android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt @@ -11,8 +11,11 @@ import com.gyf.immersionbar.ImmersionBar import com.kouyuxingqiu.wow_english.methodChannels.GameMethodChannel import com.kouyuxingqiu.wow_english.methodChannels.SingSoungMethodChannel import com.umeng.commonsdk.UMConfigure -import io.flutter.embedding.android.FlutterActivity import com.umeng.umcrash.UMCrash +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { @@ -25,7 +28,11 @@ class MainActivity : FlutterActivity() { SingSoungMethodChannel(this, it) GameMethodChannel(this, it) } - initUmeng() + } + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + initUmengMethodChannel(flutterEngine.dartExecutor.binaryMessenger) } override fun onResume() { @@ -60,11 +67,26 @@ class MainActivity : FlutterActivity() { /** * 友盟初始化 */ - private fun initUmeng() { - // 友盟集成测试阶段获取UMID -// android.util.Log.d("WQF UMConfigure", UMConfigure.getUMIDString(this)) - // 在application.onCreate内配置各模块开关并预初始化SDK - // 重点关注:如果您还想采集Native 崩溃、ANR等日志可以参考下面设置 + private var isUmengPrepared = false + + private fun initUmengMethodChannel(binaryMessenger: BinaryMessenger) { + MethodChannel(binaryMessenger, "wow_english/umeng_method_channel") + .setMethodCallHandler { call, result -> + if (call.method == "prepareAfterPrivacyConsent") { + prepareUmengAfterPrivacyConsent() + result.success(true) + } else { + result.notImplemented() + } + } + } + + private fun prepareUmengAfterPrivacyConsent() { + if (isUmengPrepared) return + + // APM 合规要求:preInit、Crash 配置和正式 init 均只能在用户同意后执行。 + UMConfigure.setLogEnabled(false) + UMConfigure.preInit(this, "663b66b0b3362515012f4ea5", "official") UMCrash.initConfig(Bundle().apply { putBoolean(UMCrash.KEY_ENABLE_CRASH_JAVA, true) putBoolean(UMCrash.KEY_ENABLE_CRASH_NATIVE, true) @@ -75,8 +97,6 @@ class MainActivity : FlutterActivity() { putBoolean(UMCrash.KEY_ENABLE_H5PAGE, false) putBoolean(UMCrash.KEY_ENABLE_POWER, false) }) - //UM日志打印 - UMConfigure.setLogEnabled(false) - UMConfigure.preInit(this, "663b66b0b3362515012f4ea5", "official") + isUmengPrepared = true } } diff --git a/lib/app/splash_page.dart b/lib/app/splash_page.dart index 5ed2f2e..2c73c2c 100644 --- a/lib/app/splash_page.dart +++ b/lib/app/splash_page.dart @@ -23,7 +23,6 @@ import '../common/core/module_cache.dart'; import '../common/widgets/webview_dialog.dart'; import '../common/utils/staticstring/userregister.dart'; - class SplashPage extends StatelessWidget { const SplashPage({super.key}); @@ -43,10 +42,14 @@ class TransitionView extends StatefulWidget { } class _TransitionViewState extends State { + bool _isPrivacyDecisionInProgress = false; + bool _isInitializing = false; + Future startTime() async { // 判断是否登录 UserEntity? userEntity = UserUtil.getUser(); - Log.d('当前模式:kDebugMode=$kDebugMode, kProfileMode=$kProfileMode, kReleaseMode=$kReleaseMode'); + Log.d( + '当前模式:kDebugMode=$kDebugMode, kProfileMode=$kProfileMode, kReleaseMode=$kReleaseMode'); Log.d('当前API地址:${RequestConfig.baseUrl}'); //BuildContext context = Navigator.of(context).context; //var currentPageName = ModalRoute.of(Navigator.of(AppRouter.context))?.settings.name; @@ -63,89 +66,151 @@ class _TransitionViewState extends State { apartInMilliseconds = DateTime.now().difference(startTime).inMilliseconds; } int duration = max(1500 - apartInMilliseconds, 0); - Log.d('Splash getUserInfo 耗时:${apartInMilliseconds}ms, 最终duration=$duration'); + Log.d( + 'Splash getUserInfo 耗时:${apartInMilliseconds}ms, 最终duration=$duration'); Timer(Duration(milliseconds: duration), () { - /*if (userEntity != null) { - pushNamedAndRemoveUntil(AppRouteName.home, (route) => false); - } else { - pushNamedAndRemoveUntil(AppRouteName.login, (route) => false); - }*/ - bool isAggreementAccepted = AppConfigHelper.getAgreementAccepted(); - if (isAggreementAccepted) { - _initData(); - } else { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return PopScope( - canPop: false, - onPopInvoked: (didPop) { - Log.d('WQF isAggreementAccepted onPopInvoked didPop=$didPop'); - }, - child: child(), - ); + pushNamedAndRemoveUntil(AppRouteName.home, (route) => false); + }); + } + + void _showPrivacyDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + Log.d('WQF privacy dialog onPopInvoked didPop=$didPop'); }, + child: child(), ); - } - }); + }, + ); } Widget child() { if (Platform.isIOS) { return AlertDialog( - title: const Center( - child: Text("服务条款及隐私政策"), - ), - content: SizedBox( + title: const Center( + child: Text("服务条款及隐私政策"), + ), + content: SizedBox( width: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height, - child: SingleChildScrollView( + child: const SingleChildScrollView( child: Text(UserRegisterString.userPrivacyPolicyStr), - ),), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - child: const Text("同意并继续", - style: TextStyle(color: Color(0xFFFBB621))), - onPressed: () { - AppConfigHelper.saveAgreementAccepted(true); - _initData(); - }, - ), - TextButton( - child: const Text("不同意,退出应用"), - onPressed: () { - // 退出应用 - AppConfigHelper.exitApp(); - }, - ), - ], - ) - ],); + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: _acceptPrivacyAgreement, + child: const Text("同意并继续", + style: TextStyle(color: Color(0xFFFBB621))), + ), + TextButton( + onPressed: _rejectPrivacyAgreement, + child: const Text("不同意,退出应用"), + ), + ], + ) + ], + ); } return WebviewDialog( - title: "服务条款及隐私政策", - webUrl: AppConsts.userPrivacyPolicyUrl, - leftButtonText: '同意并继续', - rightButtonText: '不同意,退出应用', - leftTap: () { - AppConfigHelper.saveAgreementAccepted(true); - _initData(); - }, - rightTap: () { - // 退出应用 - AppConfigHelper.exitApp(); - }, - ); + title: "服务条款及隐私政策", + webUrl: AppConsts.userPrivacyPolicyUrl, + leftButtonText: '同意并继续', + rightButtonText: '不同意,退出应用', + leftTap: _acceptPrivacyAgreement, + rightTap: _rejectPrivacyAgreement, + ); + } + + Future _acceptPrivacyAgreement() async { + if (_isPrivacyDecisionInProgress) return; + _isPrivacyDecisionInProgress = true; + + await AppConfigHelper.saveAgreementAccepted(true); + if (!mounted) return; + + // 先关闭隐私政策弹窗,再进行可能耗时的 SDK 和业务初始化。 + Navigator.of(context, rootNavigator: true).pop(); + await _startInitialization(); + _isPrivacyDecisionInProgress = false; + } + + Future _rejectPrivacyAgreement() async { + if (_isPrivacyDecisionInProgress) return; + _isPrivacyDecisionInProgress = true; + + // 显式撤销本地授权并等待写入完成,避免退出后残留 true。 + await AppConfigHelper.saveAgreementAccepted(false); + AppConfigHelper.exitApp(); + } + + Future _startInitialization() async { + if (_isInitializing) return; + _isInitializing = true; + try { + await _initData(); + } catch (error, stackTrace) { + Log.d('Splash初始化失败: $error\n$stackTrace'); + _isInitializing = false; + if (mounted) { + _showInitializationErrorDialog(); + } + } + } + + void _showInitializationErrorDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => AlertDialog( + title: const Text('温馨提示'), + content: const Text('初始化失败,请检查网络后重试'), + actions: [ + const TextButton( + onPressed: AppConfigHelper.exitApp, + child: Text('退出'), + ), + TextButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + _startInitialization(); + }, + child: const Text('重试'), + ), + ], + ), + ); } ///初始化数据 - void _initData() { - UmengCommonSdk.initCommon("663b66b0b3362515012f4ea5", "663b66ecf32cc41105ae74b7", "official"); - pushNamedAndRemoveUntil(AppRouteName.home, (route) => false); + Future _initData() async { + // 用户同意后,先准备 Native APM,再正式初始化 Common SDK。 + if (Platform.isAndroid) { + const MethodChannel umengMethodChannel = + MethodChannel('wow_english/umeng_method_channel'); + await umengMethodChannel.invokeMethod('prepareAfterPrivacyConsent'); + } + // Android 插件执行 initCommon 后不会回调 MethodChannel result,返回的 + // Future 永远不结束,因此这里只触发初始化,不能 await。 + unawaited(UmengCommonSdk.initCommon( + "663b66b0b3362515012f4ea5", "663b66ecf32cc41105ae74b7", "official") + .catchError((error, stackTrace) { + Log.d('友盟Common SDK初始化失败: $error\n$stackTrace'); + })); + await AudioPlayerUtil.getInstance() + .playAudio(AudioPlayerUtilType.welcomeToWow) + .timeout(const Duration(seconds: 10), onTimeout: () { + Log.d('欢迎音频播放超时,继续进入首页'); + }); + await startTime(); } Future fetchNecessaryData(String userToken, @@ -164,6 +229,12 @@ class _TransitionViewState extends State { completer.complete(); } catch (e) { debugPrint('Splash获取用户信息异常:$e'); + if (!mounted) { + if (!completer.isCompleted) { + completer.complete(); + } + return; + } //异常的话弹窗提示重试 showDialog( context: context, @@ -219,17 +290,26 @@ class _TransitionViewState extends State { changeDevice(); await SpUtil.preInit(); ModuleCache.instance.init(); - await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.welcomeToWow); - startTime(); + if (AppConfigHelper.getAgreementAccepted()) { + await _startInitialization(); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _showPrivacyDialog(); + } + }); + } } void changeDevice() async { ///设置设备默认方向 WidgetsFlutterBinding.ensureInitialized(); if (Platform.isIOS) { - await LimitingDirectionCsx.setScreenDirection(DeviceDirectionMask.Landscape); + await LimitingDirectionCsx.setScreenDirection( + DeviceDirectionMask.Landscape); } else { - await SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); + await SystemChrome.setPreferredOrientations( + [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); } await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } diff --git a/lib/common/core/app_config_helper.dart b/lib/common/core/app_config_helper.dart index d088356..b9a06ca 100644 --- a/lib/common/core/app_config_helper.dart +++ b/lib/common/core/app_config_helper.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -11,7 +10,6 @@ import '../../utils/sp_util.dart'; import '../request/dao/system_dao.dart'; class AppConfigHelper { - static const String _prefsKeyAgreementAccepted = "privacy_agreement_accepted"; static AppConfigEntity? configEntityEntity; @@ -22,7 +20,7 @@ class AppConfigHelper { static bool checkedUpdate = false; // 获取用户信息 - static Future getAppConfig({ bool forceSync = false }) async { + static Future getAppConfig({bool forceSync = false}) async { if (forceSync == false && configEntityEntity != null) { return configEntityEntity; } @@ -38,7 +36,8 @@ class AppConfigHelper { // 是否需要隐藏... static bool shouldHidePay() { return isIosPlatform() && - (configEntityEntity?.isAppReviewing() == true || UserUtil.getUser()?.phoneNum == "17730280759"); + (configEntityEntity?.isAppReviewing() == true || + UserUtil.getUser()?.phoneNum == "17730280759"); } // 获取app版本号 @@ -51,22 +50,19 @@ class AppConfigHelper { String versionCode = packageInfo.buildNumber; // 构建号 _versionCode = versionCode; - debugPrint('versionName=$versionName versionCode=$versionCode platForm=${Platform.operatingSystem}'); + debugPrint( + 'versionName=$versionName versionCode=$versionCode platForm=${Platform.operatingSystem}'); return versionCode; } - static void saveAgreementAccepted(bool accepted) { - SpUtil.getInstance().setData(_prefsKeyAgreementAccepted, accepted); + static Future saveAgreementAccepted(bool accepted) async { + await SpUtil.getInstance().setData(_prefsKeyAgreementAccepted, accepted); } static bool getAgreementAccepted() { return SpUtil.getInstance().get(_prefsKeyAgreementAccepted) ?? false; } - static void _clearUserData() { - SpUtil.getInstance().remove(_prefsKeyAgreementAccepted); - } - static void exitApp() { if (Platform.isIOS) { exit(0); diff --git a/lib/main.dart b/lib/main.dart index 7d73a37..442f8c4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,17 +8,6 @@ import 'package:wow_english/app/app.dart'; import 'common/request/basic_config.dart'; -// void main() { -// ///设置设备默认方向 -// WidgetsFlutterBinding.ensureInitialized(); -// if (Platform.isAndroid) { -// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( //设置状态栏透明 -// statusBarColor: Colors.transparent, -// )); -// } -// runApp(const App()); -// } - void main() { ///设置设备默认方向 if (Platform.isAndroid) { @@ -28,8 +17,8 @@ void main() { } final UmengApmSdk umengApmSdk = UmengApmSdk( - name: "", - bver: "", + name: '', + bver: '', // 是否开启SDK运行时日志输出 enableLog: BasicConfig.isTestDev, @@ -56,12 +45,15 @@ void main() { }, ); + + // 此处只安装 Flutter 侧监控能力。Native APM/Common SDK 会保持未初始化状态, + // 直到用户同意隐私政策后才由 SplashPage 正式启动。 umengApmSdk.init(appRunner: (observer) async { // 确保去掉原有的WidgetsFlutterBinding.ensureInitialized() ,以免出现重复初始化绑定的异常造成无法正常初始化,SDK内部已通过initFlutterBinding入参带入继承的WidgetsFlutterBinding实现初始化操作 // 依赖ensureInitialized()初始化的代码可在此调用 // 需要异步获取设置应用名称和版本号可在此回调中操作 // SDK实例化的设置可先将name和bver 为 "",然后通过以下方式进行设置 - PackageInfo packageInfo = await PackageInfo.fromPlatform(); + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); String packageName = packageInfo.packageName; String buildNumber = packageInfo.buildNumber; String version = packageInfo.version; diff --git a/lib/utils/sp_util.dart b/lib/utils/sp_util.dart index 855fa3c..17fa2de 100644 --- a/lib/utils/sp_util.dart +++ b/lib/utils/sp_util.dart @@ -28,17 +28,17 @@ class SpUtil { return _instance!; } - void setData(String key, T data) { + Future setData(String key, T data) async { if (data is String) { - _prefs?.setString(key, data); + await _prefs?.setString(key, data); } else if (data is double) { - _prefs?.setDouble(key, data); + await _prefs?.setDouble(key, data); } else if (data is int) { - _prefs?.setInt(key, data); + await _prefs?.setInt(key, data); } else if (data is bool) { - _prefs?.setBool(key, data); + await _prefs?.setBool(key, data); } else if (data is List) { - _prefs?.setStringList(key, data); + await _prefs?.setStringList(key, data); } } diff --git a/pubspec.yaml b/pubspec.yaml index a990365..991b20e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -113,7 +113,7 @@ dependencies: # 应用内更新 https://pub-web.flutter-io.cn/packages/flutter_app_update flutter_app_update: ^3.0.4 # 友盟统计 https://pub-web.flutter-io.cn/packages/umeng_common_sdk - umeng_common_sdk: ^1.2.8 + umeng_common_sdk: ^1.3.1 # 友盟APM https://pub-web.flutter-io.cn/packages/umeng_apm_sdk umeng_apm_sdk: ^2.3.2 # 嵌套滚动 https://pub.dev/packages/nested_scroll_views -- libgit2 0.22.2