优化:日志输出配置

This commit is contained in:
ivan 2026-04-16 17:00:55 +08:00
parent 59ab3b247a
commit 66bbf38c64
11 changed files with 346 additions and 8 deletions

View File

@ -4,7 +4,9 @@ import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import com.facebook.FacebookSdk import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@ -54,12 +56,25 @@ class ClientProxyFrameworkPlugin : FlutterPlugin, MethodChannel.MethodCallHandle
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) { when (call.method) {
"setFacebookSdkDebugLogging" -> {
val ctx = applicationContext
if (ctx == null) {
result.error("NO_CONTEXT", "applicationContext null", null)
return
}
val enabled = call.argument<Boolean>("enabled") ?: false
applyFacebookSdkDebugLogging(enabled)
result.success(null)
}
"waitForFacebookSdkInit" -> { "waitForFacebookSdkInit" -> {
val ctx = applicationContext val ctx = applicationContext
if (ctx == null) { if (ctx == null) {
result.error("NO_CONTEXT", "applicationContext null", null) result.error("NO_CONTEXT", "applicationContext null", null)
return return
} }
val args = call.arguments as? Map<*, *>
val debugLogs = coerceFacebookSdkDebugLogsArg(args?.get("facebookSdkDebugLogs"))
applyFacebookSdkDebugLogging(debugLogs)
val mainHandler = Handler(Looper.getMainLooper()) val mainHandler = Handler(Looper.getMainLooper())
try { try {
// SDK 18 无 addInitializedCallback用 sdkInitialize(..., InitializeCallback) 在就绪后回调 // SDK 18 无 addInitializedCallback用 sdkInitialize(..., InitializeCallback) 在就绪后回调
@ -71,6 +86,8 @@ class ClientProxyFrameworkPlugin : FlutterPlugin, MethodChannel.MethodCallHandle
override fun onInitialized() { override fun onInitialized() {
mainHandler.post { mainHandler.post {
try { try {
// 再应用一次:避免 FacebookInitProvider / 其他插件抢先初始化导致标志被吃掉
applyFacebookSdkDebugLogging(debugLogs)
channel?.invokeMethod("onFacebookSdkInitialized", null) channel?.invokeMethod("onFacebookSdkInitialized", null)
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
@ -88,6 +105,41 @@ class ClientProxyFrameworkPlugin : FlutterPlugin, MethodChannel.MethodCallHandle
} }
} }
/**
* 由宿主 `skin_config` `facebook.debugLogs` MethodChannel `setFacebookSdkDebugLogging` 控制
* 关闭时恢复为 SDK 默认仅保留 [LoggingBehavior.DEVELOPER_ERRORS]
*/
private fun applyFacebookSdkDebugLogging(enabled: Boolean) {
FacebookSdk.setIsDebugEnabled(enabled)
FacebookSdk.clearLoggingBehaviors()
if (enabled) {
FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
FacebookSdk.addLoggingBehavior(LoggingBehavior.REQUESTS)
FacebookSdk.addLoggingBehavior(LoggingBehavior.DEVELOPER_ERRORS)
FacebookSdk.addLoggingBehavior(LoggingBehavior.GRAPH_API_DEBUG_INFO)
Log.i(
"ClientProxyFB",
"Facebook SDK debug logging ON — also filter Logcat: tag FacebookSDK or Request",
)
} else {
FacebookSdk.addLoggingBehavior(LoggingBehavior.DEVELOPER_ERRORS)
Log.i("ClientProxyFB", "Facebook SDK debug logging OFF (verbose behaviors cleared)")
}
}
/** Flutter Map 里可能是 Bool / Int / String避免一直落在默认 false。 */
private fun coerceFacebookSdkDebugLogsArg(raw: Any?): Boolean {
return when (raw) {
is Boolean -> raw
is Number -> raw.toInt() != 0
is String -> {
val s = raw.trim().lowercase()
s == "true" || s == "1" || s == "yes"
}
else -> false
}
}
companion object { companion object {
const val CHANNEL_NAME = "client_proxy_framework/facebook_sdk" const val CHANNEL_NAME = "client_proxy_framework/facebook_sdk"
const val DEVICE_MEMORY_CHANNEL_NAME = "client_proxy_framework/device_memory" const val DEVICE_MEMORY_CHANNEL_NAME = "client_proxy_framework/device_memory"

View File

@ -19,6 +19,8 @@
按模块整理的“功能 + 最小调用示例”请见 **[《Framework 功能与用法总览》](framework_feature_usage.md)**。 按模块整理的“功能 + 最小调用示例”请见 **[《Framework 功能与用法总览》](framework_feature_usage.md)**。
以首款换皮应用 **FunyMee** 为参考的 **视频首页**`common_info` → 分类 → 按分类拉模板)数据流说明,见 **[《视频首页数据获取流程》](video_home_data_flow.md)**。
--- ---
## 2. 快速开始 ## 2. 快速开始

View File

@ -0,0 +1,134 @@
# 视频首页数据获取流程(以首款换皮应用 FunyMee 为参考)
本文描述**框架侧**在典型换皮应用(首款为 **FunyMee**)中,从冷启动到「视频首页」可展示分类与模板列表的**数据链路与调用顺序**。FunyMee 宿主 UI 不在本仓库内;流程以 `client_proxy_framework` 中的 `FrameworkAuthService``ExtConfigRuntime``VideoHomeRuntime``ImageApi` 为准,与代码注释中引用的线网接口(如 `GET /v1/image/img2video/tasks`)一致。
---
## 1. 前提与配置
1. **`ClientBootstrap.initFromAsset`**:加载 `skin_config.json`,构建 `SkinConfig``ApiClient.init`使后续请求走统一代理、AES 与 V2 包装(见 `lib/src/bootstrap/client_bootstrap.dart`)。
2. **`FrameworkAuthService.init` + `start`**:宿主实现 `AuthServiceCallbacks`(设备 ID、签名等由框架编排登录与 `common_info`(见 `lib/src/services/auth_service.dart`)。
3. **`skin_config.json`** 中与首页相关的片段:
- **`extConfig`**`keys.showVideoMenu` 映射到线网布尔(示例中为 `go_run` / `need_wait` 等,见 `lib/src/config/skin_config.example.json``items``taskItemMapping` 决定 `common_info` 下发的运营位卡片如何解析为与任务列表同形的 `ExtConfigItem`
- **`videoHome`**`imagesTabLabel``imagesTabFirst` 控制「Images」虚拟 Tab 的文案及与接口分类的排序(见 `lib/src/config/skin_config.dart`)。
4. **`fieldMapping`**FunyMee 等换皮下线网字段与逻辑字段不一致时,响应体会先经映射再进入实体解析(注释示例见 `lib/src/services/image_api.dart`)。
---
## 2. 总览时序
```mermaid
sequenceDiagram
participant Host as 宿主 main / UI
participant Boot as ClientBootstrap
participant Auth as FrameworkAuthService
participant User as UserApi / ProxyClient
participant Ext as ExtConfigRuntime
participant VH as VideoHomeRuntime
participant Img as ImageApi
Host->>Boot: initFromAsset(skin_config)
Host->>Auth: init(callbacks); start()
Auth->>User: POST /v1/user/fast_login
User-->>Auth: userToken, userId
Auth->>User: GET /v1/user/common_info
User-->>Auth: extConfig 等
Auth->>Ext: applyCommonInfoSuccess
Auth->>VH: hydrateAfterCommonInfo (异步)
VH->>Img: GET /v1/image/img2video/categories
Img-->>VH: 分类列表
VH->>VH: 合并 Tabs接口分类 + ext items → Images
VH->>Img: GET /v1/image/img2video/tasks?categoryId=…
Img-->>VH: 当前分类模板列表
```
---
## 3. 分阶段说明
### 3.1 启动与快速登录
- 延迟与重试策略由 `FrameworkAuthService.start` 控制(默认启动延迟、登录重试等)。
- **`UserApi.fastLogin`**`POST /v1/user/fast_login`。请求头**仅**带 `pkg`**不带** `User_token`(见 `UserApi` 文档注释)。
- 成功后框架将返回的 `userToken` 写入 **`ApiClient.instance.setUserToken`**,此后代理请求自动附带 `pkg``User_token`(见 `ProxyClient` 行为与 `UserApi` 说明)。
### 3.2 归因上报(与首页数据并行准备)
- 在拉取 `common_info` 之前,若存在 Adjust / Play 等 referrer会依次调用 **`UserApi.referrer`**`POST /v1/user/referrer`)上报,不阻塞后续 `common_info` 的成功与否结论,但属于同一 `_reportReferrersAndLoadCommonInfo` 流程(见 `auth_service.dart`)。
### 3.3 通用信息与 extConfig
- **`UserApi.getCommonInfo`**`GET /v1/user/common_info`query 含 `app`iOS/Android 后端渠道,与 `skin_config.backend` 一致)、`pkg``userId``deviceId` 等(见 `user_api.dart`)。
- 成功时调用 **`ExtConfigRuntime.applyCommonInfoSuccess`**
- 将 `extConfig` 与本地 `extConfig.defaults` 浅合并;
- 按 `extConfigKeySchema` 解析为 **`ExtConfigData`**(含 `showVideoMenu``items` 等)(见 `ext_config_runtime.dart``ext_config_models.dart`)。
- **`ExtConfigRuntime.commonInfoSucceeded`**:宿主可用「登录完成且 `common_info` 成功」再展示主业务界面(见 `ext_config_runtime.dart` 注释建议)。
### 3.4 视频首页运行时水合hydrate
`common_info` **成功**后,`FrameworkAuthService` 会 **fire-and-forget** 调用 **`VideoHomeRuntime.hydrateAfterCommonInfo`**(不阻塞 `loginComplete` 的完成)(见 `auth_service.dart`)。
`hydrateAfterCommonInfo` 的进入条件与行为(见 `video_home_runtime.dart`
| 条件 | 行为 |
|------|------|
| `userId` 为空,或 `ExtConfigData.showVideoMenu != true` | **`VideoHomeRuntime.reset`**,不拉分类、无 images Tab |
| 否则 | 置 `snapshot.loading = true`,再请求分类列表 |
水合步骤概要:
1. **`ImageApi.getCategoryList`** → `GET /v1/image/img2video/categories`,得到服务端分类(`CategoryItem`,含 `id` / `name`)。
2. 从 **`ExtConfigRuntime.data`** 读取 `items`,过滤 **`ExtConfigItem.isUsableOnHome`**,非空则存在 **Images** 虚拟 Tab文案为 `AppConfig.videoHomeImagesTabLabel`(来自 `skin_config.videoHome.imagesTabLabel`)。
3. **Tab 顺序**:由 `videoHomeImagesTabFirst` 决定是先 Images 还是先接口分类。
4. **默认选中 Tab**:默认选中**第一个服务端分类**(非 Images若仅有 Images Tab 则下标为 0并发水合与用户提前切换时有保护逻辑`video_home_runtime.dart` 内注释)。
5. 水合结束后调用 **`VideoHomeRuntime.ensureTabItems(selectedTabIndex)`**,为当前 Tab 拉取模板数据(见下节)。
若分类接口失败且没有任何 Tab 可构建,`snapshot.error` 会携带失败信息。
### 3.5 按 Tab 拉取模板列表
- **Images Tab**:数据直接来自 **`ExtConfigData.items`**(已在水合前解析),**不再**请求 `img2video/tasks`
- **服务端分类 Tab**:首次选中该 Tab 时,**`VideoHomeRuntime.ensureTabItems`** 调用 **`ImageApi.getImg2VideoTasks(categoryId: id)`** → `GET /v1/image/img2video/tasks?categoryId=...`(与 FunyMee 文档一致)。结果通过 `ExtConfigItem.fromTaskItem` 写入 **`VideoHomeSnapshot.networkItemsByCategoryId`**,并按分类 id 缓存,避免重复请求。
---
## 4. 宿主 UI 对接要点
- **监听状态**`VideoHomeRuntime.snapshot``VideoHomeRuntime.selectedTabIndex``ValueNotifier`,可用 `ValueListenableBuilder` 或类似方式驱动顶栏 Tab 与内容区。
- **切换 Tab**:切换 `selectedTabIndex` 后应调用 **`VideoHomeRuntime.ensureTabItems(newIndex)`**(框架水合末尾已对初始 Tab 调用一次),以按需加载该分类下的模板列表。
- **登录 / common_info 失败**`ExtConfigRuntime.commonInfoSucceeded == false``userId` 为空时,不会进入视频首页水合;宿主应展示错误态或重试入口。
---
## 5. 相关 HTTP 接口汇总
| 顺序 | 方法 | 路径 | 用途 |
|------|------|------|------|
| 1 | POST | `/v1/user/fast_login` | 设备登录,取得 token |
| 2 | POST | `/v1/user/referrer` | 可选,归因上报 |
| 3 | GET | `/v1/user/common_info` | 开关与 `extConfig`(含首页运营位 `items` |
| 4 | GET | `/v1/image/img2video/categories` | 视频首页顶栏服务端分类 |
| 5 | GET | `/v1/image/img2video/tasks` | 某分类下模板列表query`categoryId` |
所有经 `ProxyClient` 的业务请求均走 **`skin_config` 中的 `proxyPath`**body 为加密后的代理载荷;响应经解密与 **`fieldMapping`** 还原为逻辑字段后再解析实体。
---
## 6. 代码索引(便于跳转)
| 模块 | 路径 |
|------|------|
| 启动换皮配置 | `lib/src/bootstrap/client_bootstrap.dart` |
| 登录与 common_info 编排 | `lib/src/services/auth_service.dart` |
| 用户接口 | `lib/src/services/user_api.dart` |
| 图/视频分类与任务列表 | `lib/src/services/image_api.dart` |
| extConfig 运行时 | `lib/src/config/ext_config_runtime.dart` |
| 视频首页 Tab 与缓存 | `lib/src/config/video_home_runtime.dart` |
| extConfig / items 解析 | `lib/src/config/ext_config_models.dart` |
| JSON 配置示例 | `lib/src/config/skin_config.example.json` |
---
## 7. 与《创建新换皮应用》的关系
从零搭建宿主工程、资产路径与 `main` 初始化顺序,仍以 **[create_new_skin_app.md](create_new_skin_app.md)** 为准;本文仅补充 **「视频首页」在框架内的数据流**,与 FunyMee 首推路线一致。

View File

@ -1,5 +1,7 @@
import Flutter import Flutter
import UIKit import UIKit
import FBSDKCoreKit
import os.log
public class ClientProxyFrameworkPlugin: NSObject, FlutterPlugin { public class ClientProxyFrameworkPlugin: NSObject, FlutterPlugin {
private var channel: FlutterMethodChannel? private var channel: FlutterMethodChannel?
@ -14,11 +16,53 @@ public class ClientProxyFrameworkPlugin: NSObject, FlutterPlugin {
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "waitForFacebookSdkInit" { switch call.method {
case "setFacebookSdkDebugLogging":
let args = call.arguments as? [String: Any]
let enabled = Self.coerceFacebookSdkDebugLogsArg(args?["enabled"])
Self.applyFacebookSdkDebugLogging(enabled: enabled)
result(nil)
case "waitForFacebookSdkInit":
let args = call.arguments as? [String: Any]
let debugLogs = Self.coerceFacebookSdkDebugLogsArg(args?["facebookSdkDebugLogs"])
Self.applyFacebookSdkDebugLogging(enabled: debugLogs)
result(true) result(true)
channel?.invokeMethod("onFacebookSdkInitialized", arguments: nil) channel?.invokeMethod("onFacebookSdkInitialized", arguments: nil)
} else { default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
} }
/// Android `facebook.debugLogs` Dart `setFacebookSdkDebugLogging`
private static func applyFacebookSdkDebugLogging(enabled: Bool) {
if enabled {
Settings.shared.loggingBehaviors = Set([
.appEvents,
.networkRequests,
.informational,
.developerErrors,
.graphAPIDebugInfo,
])
os_log("Facebook SDK debug logging ON (filter console for FBSDK / Facebook)", log: .default, type: .info)
print("ClientProxyFB: Facebook SDK debug logging ON — also search Xcode console for FBSDK / FacebookSDK")
} else {
Settings.shared.loggingBehaviors = []
os_log("Facebook SDK debug logging OFF", log: .default, type: .info)
print("ClientProxyFB: Facebook SDK debug logging OFF")
}
}
private static func coerceFacebookSdkDebugLogsArg(_ raw: Any?) -> Bool {
switch raw {
case let b as Bool:
return b
case let n as NSNumber:
return n.intValue != 0
case let s as String:
let v = s.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return v == "true" || v == "1" || v == "yes"
default:
return false
}
}
} }

View File

@ -11,6 +11,8 @@ Pod::Spec.new do |s|
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.dependency 'Flutter' s.dependency 'Flutter'
# 与 facebook_app_events 一致,便于在桥接层开启 FBSDK 调试日志
s.dependency 'FBSDKCoreKit', '~> 18.0'
s.platform = :ios, '13.0' s.platform = :ios, '13.0'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0' s.swift_version = '5.0'

View File

@ -32,6 +32,7 @@ class FacebookConfig {
const FacebookConfig({ const FacebookConfig({
required this.appId, required this.appId,
this.clientToken, this.clientToken,
/// ** Facebook SDK** Logcat / Xcode [AnalyticsConfig.debugLogs]
this.debugLogs = false, this.debugLogs = false,
}); });

View File

@ -351,7 +351,7 @@ class SkinConfig implements AppConfig {
fbCfg = FacebookConfig( fbCfg = FacebookConfig(
appId: id.trim(), appId: id.trim(),
clientToken: ct.isEmpty ? null : ct, clientToken: ct.isEmpty ? null : ct,
debugLogs: fb['debugLogs'] as bool? ?? false, debugLogs: _coerceConfigBool(fb['debugLogs']),
); );
} else if (id.trim().isNotEmpty || clientTok.trim().isNotEmpty) { } else if (id.trim().isNotEmpty || clientTok.trim().isNotEmpty) {
SdkReminderLog.facebook( SdkReminderLog.facebook(
@ -415,4 +415,16 @@ class SkinConfig implements AppConfig {
} }
return const PlatformAttributionConfig(); return const PlatformAttributionConfig();
} }
/// JSON / `1`/`"true"` `as bool?` false
static bool _coerceConfigBool(dynamic value, {bool fallback = false}) {
if (value == true || value == 1) return true;
if (value == false || value == 0) return false;
if (value is String) {
final s = value.trim().toLowerCase();
if (s == 'true' || s == '1' || s == 'yes') return true;
if (s == 'false' || s == '0' || s == 'no') return false;
}
return fallback;
}
} }

View File

@ -96,7 +96,14 @@ void _logLong(String text) {
/// JSON /// JSON
void logWithEmbeddedJson(Object? msg) { void logWithEmbeddedJson(Object? msg) {
if (!kDebugMode) return; if (kReleaseMode) {
final releaseLevel =
const String.fromEnvironment('APP_LOG_LEVEL').trim().toLowerCase();
final allowVerbose = releaseLevel == 'all' ||
releaseLevel == 'trace' ||
releaseLevel == 'debug';
if (!allowVerbose) return;
}
if (msg is! String) { if (msg is! String) {
_proxyLog.d(msg); _proxyLog.d(msg);
@ -184,6 +191,38 @@ class AppLogger {
static Logger? _logger; static Logger? _logger;
static bool _releaseVerboseEnabled() {
if (!kReleaseMode) return false;
final raw = const String.fromEnvironment('APP_LOG_LEVEL').trim().toLowerCase();
return raw == 'all' || raw == 'trace' || raw == 'debug';
}
static Level _resolveLogLevel() {
final raw = const String.fromEnvironment('APP_LOG_LEVEL').trim().toLowerCase();
switch (raw) {
case 'all':
case 'trace':
return Level.trace;
case 'debug':
return Level.debug;
case 'info':
return Level.info;
case 'warning':
case 'warn':
return Level.warning;
case 'error':
return Level.error;
case 'fatal':
case 'wtf':
return Level.fatal;
case 'off':
case 'none':
return Level.off;
default:
return kReleaseMode ? Level.warning : Level.trace;
}
}
static Logger get _instance { static Logger get _instance {
_logger ??= Logger( _logger ??= Logger(
printer: PrettyPrinter( printer: PrettyPrinter(
@ -194,14 +233,22 @@ class AppLogger {
printEmojis: true, printEmojis: true,
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart, dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
), ),
level: kReleaseMode ? Level.warning : Level.trace, level: _resolveLogLevel(),
); );
return _logger!; return _logger!;
} }
String _msg(Object? message) => '[$tag] $message'; String _msg(Object? message) => '[$tag] $message';
void d(Object? message) => _instance.d(_msg(message)); void d(Object? message) {
final m = _msg(message);
if (_releaseVerboseEnabled()) {
// Release + trace/debug logger
debugPrint(m);
return;
}
_instance.d(m);
}
void i(Object? message) => _instance.i(_msg(message)); void i(Object? message) => _instance.i(_msg(message));
void w(Object? message) => _instance.w(_msg(message)); void w(Object? message) => _instance.w(_msg(message));
void e(Object? message, [Object? error, StackTrace? stackTrace]) => void e(Object? message, [Object? error, StackTrace? stackTrace]) =>

View File

@ -78,7 +78,7 @@ abstract final class AnalyticsEvents {
} }
} }
/// Adjust `purchase` `firstPurchase`Facebook [AnalyticsService.trackPurchase] /// Adjust `purchase` `firstPurchase` + Facebook `FirstRecharge` app_client [AdjustEvents.trackFirstPurchase] Facebook [AnalyticsService.trackPurchase]
static Future<void> trackPurchaseSuccess(double amount) async { static Future<void> trackPurchaseSuccess(double amount) async {
_skin.trackAdjustEvent('purchase'); _skin.trackAdjustEvent('purchase');
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@ -87,6 +87,10 @@ abstract final class AnalyticsEvents {
final today = DateTime.now().toIso8601String().substring(0, 10); final today = DateTime.now().toIso8601String().substring(0, 10);
if (regDate != null && regDate == today) { if (regDate != null && regDate == today) {
_skin.trackAdjustEvent('firstPurchase'); _skin.trackAdjustEvent('firstPurchase');
FacebookService.logEvent(
'FirstRecharge',
parameters: <String, dynamic>{'amount': amount},
);
} }
if (amount > 0) { if (amount > 0) {
AnalyticsService.trackPurchase(amount: amount, currency: 'USD'); AnalyticsService.trackPurchase(amount: amount, currency: 'USD');

View File

@ -152,6 +152,8 @@ abstract class FrameworkAuthService {
} }
if (res == null) { if (res == null) {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
completer.complete(); completer.complete();
return; return;
} }
@ -180,9 +182,13 @@ abstract class FrameworkAuthService {
deviceId: deviceId, deviceId: deviceId,
); );
} else { } else {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
_callbacks!.onLoginFailed(res.msg); _callbacks!.onLoginFailed(res.msg);
} }
} catch (e, st) { } catch (e, st) {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
if (kDebugMode) { if (kDebugMode) {
debugPrint('[AuthService] start: 异常 $e\n$st'); debugPrint('[AuthService] start: 异常 $e\n$st');
} }

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:facebook_app_events/facebook_app_events.dart'; import 'package:facebook_app_events/facebook_app_events.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../config/attribution_config.dart'; import '../config/attribution_config.dart';
@ -43,9 +44,22 @@ class FacebookService {
}); });
await _channel! await _channel!
.invokeMethod<void>('waitForFacebookSdkInit') .invokeMethod<void>(
'waitForFacebookSdkInit',
<String, dynamic>{
'facebookSdkDebugLogs': config.debugLogs,
},
)
.timeout(_nativeInitTimeout); .timeout(_nativeInitTimeout);
_log.d('waitForFacebookSdkInit finished'); _log.d('waitForFacebookSdkInit finished');
if (config.debugLogs) {
debugPrint(
'[Facebook] 已请求原生打开 Facebook SDK 调试日志skin: analytics.facebook.debugLogs'
' 若看不到 FacebookSDK 相关行:请「完全重启」应用(改 asset 后热重载无效)、'
'Android Logcat 级别选 Verbose/Debug 并搜索 FacebookSDK 或 ClientProxyFB'
' 关闭自动 App Events 时需有网络请求或手动 logEvent/activateApp 才会有大量 REQUESTS 日志。',
);
}
} on TimeoutException catch (e, st) { } on TimeoutException catch (e, st) {
SdkReminderLog.facebook( SdkReminderLog.facebook(
'原生初始化超时 ($e),已跳过。请检查是否集成框架 Android/iOS 插件、`AndroidManifest` / Info.plist 中 Facebook 配置是否完整。\n$st', '原生初始化超时 ($e),已跳过。请检查是否集成框架 Android/iOS 插件、`AndroidManifest` / Info.plist 中 Facebook 配置是否完整。\n$st',
@ -95,6 +109,26 @@ class FacebookService {
} }
} }
/// 宿 Facebook SDK [FacebookConfig.debugLogs]
///
/// **** [init] [init]
/// `skin_config` `analytics.facebook.debugLogs` `true`
static Future<void> setFacebookSdkDebugLogging(bool enabled) async {
try {
_channel ??= MethodChannel(kFacebookSdkChannelName);
await _channel!.invokeMethod<void>(
'setFacebookSdkDebugLogging',
<String, dynamic>{'enabled': enabled},
);
} on MissingPluginException catch (_) {
SdkReminderLog.facebook(
'setFacebookSdkDebugLogging未注册原生插件已忽略。',
);
} catch (e, st) {
SdkReminderLog.facebook('setFacebookSdkDebugLogging 失败: $e\n$st');
}
}
/// `AndroidManifest` / `Info.plist` App Events /// `AndroidManifest` / `Info.plist` App Events
static void activateApp() { static void activateApp() {
try { try {