优化:日志输出配置
This commit is contained in:
parent
59ab3b247a
commit
66bbf38c64
@ -4,7 +4,9 @@ import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.facebook.FacebookSdk
|
||||
import com.facebook.LoggingBehavior
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
@ -54,12 +56,25 @@ class ClientProxyFrameworkPlugin : FlutterPlugin, MethodChannel.MethodCallHandle
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
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" -> {
|
||||
val ctx = applicationContext
|
||||
if (ctx == null) {
|
||||
result.error("NO_CONTEXT", "applicationContext null", null)
|
||||
return
|
||||
}
|
||||
val args = call.arguments as? Map<*, *>
|
||||
val debugLogs = coerceFacebookSdkDebugLogsArg(args?.get("facebookSdkDebugLogs"))
|
||||
applyFacebookSdkDebugLogging(debugLogs)
|
||||
val mainHandler = Handler(Looper.getMainLooper())
|
||||
try {
|
||||
// SDK 18 无 addInitializedCallback;用 sdkInitialize(..., InitializeCallback) 在就绪后回调
|
||||
@ -71,6 +86,8 @@ class ClientProxyFrameworkPlugin : FlutterPlugin, MethodChannel.MethodCallHandle
|
||||
override fun onInitialized() {
|
||||
mainHandler.post {
|
||||
try {
|
||||
// 再应用一次:避免 FacebookInitProvider / 其他插件抢先初始化导致标志被吃掉
|
||||
applyFacebookSdkDebugLogging(debugLogs)
|
||||
channel?.invokeMethod("onFacebookSdkInitialized", null)
|
||||
result.success(true)
|
||||
} 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 {
|
||||
const val CHANNEL_NAME = "client_proxy_framework/facebook_sdk"
|
||||
const val DEVICE_MEMORY_CHANNEL_NAME = "client_proxy_framework/device_memory"
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
按模块整理的“功能 + 最小调用示例”请见 **[《Framework 功能与用法总览》](framework_feature_usage.md)**。
|
||||
|
||||
以首款换皮应用 **FunyMee** 为参考的 **视频首页**(`common_info` → 分类 → 按分类拉模板)数据流说明,见 **[《视频首页数据获取流程》](video_home_data_flow.md)**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 快速开始
|
||||
|
||||
134
docs/video_home_data_flow.md
Normal file
134
docs/video_home_data_flow.md
Normal 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 首推路线一致。
|
||||
@ -1,5 +1,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import FBSDKCoreKit
|
||||
import os.log
|
||||
|
||||
public class ClientProxyFrameworkPlugin: NSObject, FlutterPlugin {
|
||||
private var channel: FlutterMethodChannel?
|
||||
@ -14,11 +16,53 @@ public class ClientProxyFrameworkPlugin: NSObject, FlutterPlugin {
|
||||
}
|
||||
|
||||
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)
|
||||
channel?.invokeMethod("onFacebookSdkInitialized", arguments: nil)
|
||||
} else {
|
||||
default:
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ Pod::Spec.new do |s|
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
# 与 facebook_app_events 一致,便于在桥接层开启 FBSDK 调试日志
|
||||
s.dependency 'FBSDKCoreKit', '~> 18.0'
|
||||
s.platform = :ios, '13.0'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
||||
s.swift_version = '5.0'
|
||||
|
||||
@ -32,6 +32,7 @@ class FacebookConfig {
|
||||
const FacebookConfig({
|
||||
required this.appId,
|
||||
this.clientToken,
|
||||
/// 是否打开 **原生 Facebook SDK** 详细日志(Logcat / Xcode 控制台),与 [AnalyticsConfig.debugLogs] 独立。
|
||||
this.debugLogs = false,
|
||||
});
|
||||
|
||||
|
||||
@ -351,7 +351,7 @@ class SkinConfig implements AppConfig {
|
||||
fbCfg = FacebookConfig(
|
||||
appId: id.trim(),
|
||||
clientToken: ct.isEmpty ? null : ct,
|
||||
debugLogs: fb['debugLogs'] as bool? ?? false,
|
||||
debugLogs: _coerceConfigBool(fb['debugLogs']),
|
||||
);
|
||||
} else if (id.trim().isNotEmpty || clientTok.trim().isNotEmpty) {
|
||||
SdkReminderLog.facebook(
|
||||
@ -415,4 +415,16 @@ class SkinConfig implements AppConfig {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,14 @@ void _logLong(String text) {
|
||||
|
||||
/// 格式化输出嵌入在字符串中的 JSON,保持缩进对齐。
|
||||
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) {
|
||||
_proxyLog.d(msg);
|
||||
@ -184,6 +191,38 @@ class AppLogger {
|
||||
|
||||
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 {
|
||||
_logger ??= Logger(
|
||||
printer: PrettyPrinter(
|
||||
@ -194,14 +233,22 @@ class AppLogger {
|
||||
printEmojis: true,
|
||||
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
|
||||
),
|
||||
level: kReleaseMode ? Level.warning : Level.trace,
|
||||
level: _resolveLogLevel(),
|
||||
);
|
||||
return _logger!;
|
||||
}
|
||||
|
||||
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 w(Object? message) => _instance.w(_msg(message));
|
||||
void e(Object? message, [Object? error, StackTrace? stackTrace]) =>
|
||||
|
||||
@ -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 {
|
||||
_skin.trackAdjustEvent('purchase');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@ -87,6 +87,10 @@ abstract final class AnalyticsEvents {
|
||||
final today = DateTime.now().toIso8601String().substring(0, 10);
|
||||
if (regDate != null && regDate == today) {
|
||||
_skin.trackAdjustEvent('firstPurchase');
|
||||
FacebookService.logEvent(
|
||||
'FirstRecharge',
|
||||
parameters: <String, dynamic>{'amount': amount},
|
||||
);
|
||||
}
|
||||
if (amount > 0) {
|
||||
AnalyticsService.trackPurchase(amount: amount, currency: 'USD');
|
||||
|
||||
@ -152,6 +152,8 @@ abstract class FrameworkAuthService {
|
||||
}
|
||||
|
||||
if (res == null) {
|
||||
VideoHomeRuntime.reset();
|
||||
ExtConfigRuntime.applyCommonInfoFailure();
|
||||
completer.complete();
|
||||
return;
|
||||
}
|
||||
@ -180,9 +182,13 @@ abstract class FrameworkAuthService {
|
||||
deviceId: deviceId,
|
||||
);
|
||||
} else {
|
||||
VideoHomeRuntime.reset();
|
||||
ExtConfigRuntime.applyCommonInfoFailure();
|
||||
_callbacks!.onLoginFailed(res.msg);
|
||||
}
|
||||
} catch (e, st) {
|
||||
VideoHomeRuntime.reset();
|
||||
ExtConfigRuntime.applyCommonInfoFailure();
|
||||
if (kDebugMode) {
|
||||
debugPrint('[AuthService] start: 异常 $e\n$st');
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:facebook_app_events/facebook_app_events.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../config/attribution_config.dart';
|
||||
@ -43,9 +44,22 @@ class FacebookService {
|
||||
});
|
||||
|
||||
await _channel!
|
||||
.invokeMethod<void>('waitForFacebookSdkInit')
|
||||
.invokeMethod<void>(
|
||||
'waitForFacebookSdkInit',
|
||||
<String, dynamic>{
|
||||
'facebookSdkDebugLogs': config.debugLogs,
|
||||
},
|
||||
)
|
||||
.timeout(_nativeInitTimeout);
|
||||
_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) {
|
||||
SdkReminderLog.facebook(
|
||||
'原生初始化超时 ($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 时常用)。
|
||||
static void activateApp() {
|
||||
try {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user