项目广告聚合(Taku/AnyThink)架构分析

方木先生
分享互动规则

一、概述

本项目使用 AnyThink(Taku/安速) 作为广告聚合平台。AnyThink 是一个国内广告聚合中间件,可以同时聚合多家广告 SDK(Sigmob、CSJ/Pangle、Kuying/SDM),由广告主/运营通过后台配置流量分配,客户端统一接入。

广告系统分为三个层次:

应用层 (lib/ad/)              → 管理广告生命周期,决定展示时机
桥梁层 (packages/anythink_sdk/) → Flutter Plugin,桥接 MethodChannel
原生层 (Android/iOS SDK)       → 各广告 SDK 原生实现

当前项目仅使用了 开屏广告(Splash Ad) 一种广告形式。广告配置完全由服务端下发,支持按平台(Android/iOS)区分。


二、整体架构

┌─────────────────────────────────────────────────────────────┐
│                     AppBootstrapper                          │
│  lib/startup/app_bootstrapper.dart                           │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ ① initSDKAsync(initAdSDK: true)                      │   │
│  │   ├── 基础 SDK 初始化                                    │   │
│  │   └── _initATT() → _initAdSDK()                      │   │
│  │        └── 服务端获取 AdConf 配置 → AdTakuSdk.init()    │   │
│  │                                                        │   │
│  │ ② _resolveStartPage()                                  │   │
│  │   └── ATT + adConf → AdTakuSplashWidget               │   │
│  └──────────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────┐
│                AdTakuSplashWidget (Widget)                    │
│  lib/ad/ad_taku_splash_widget.dart                           │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ ① 展示静态开屏背景图                                    │   │
│  │ ② AdTakuSplash.instance.showIfReadyOtherwiseLoad()    │   │
│  │ ③ 任意失败 → _enterAppOnce() 进入主页                   │   │
│  └──────────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────┐
│          AdTakuSplash (控制器)                                │
│  lib/ad/ad_taku_splash.dart                                  │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ ① initListener() → 订阅 ATListenerManager             │   │
│  │ ② load() → ATSplashManager.loadSplash()               │   │
│  │ ③ show() → ATSplashManager.showSplash()               │   │
│  │ ④ 通过 Completer 实现加载等待 + 超时             │   │
│  │ ⑤ 路由事件到 SplashCallbacks                         │   │
│  └──────────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────┐
│                anythink_sdk (Flutter Plugin)                  │
│  packages/anythink_sdk-1.0.8                                 │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ MethodChannel('anythink_sdk')                         │   │
│  │                                                        │   │
│  │ Dart 端:                                                │   │
│  │  ├── ATInit → initAnyThinkSDK()                        │   │
│  │  ├── ATSplash → loadSplash() / showSplash()            │   │
│  │  └── ATListener → 事件回调分发 (splashEventHandler)    │   │
│  │                                                        │   │
│  │ ↓ MethodChannel invoke  /  ↑ MethodCallHandler         │   │
│  │                                                        │   │
│  │ 原生端:                                                 │   │
│  │  ├── Android: ATSplashHelper + FrameLayout 渲染        │   │
│  │  └── iOS: ATFSplashAdManger + ATFSplashDelegate        │   │
│  └──────────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────┐
│             原生广告 SDK (聚合网络)                            │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ Taku Core 6.5.70                                      │   │
│  │  ├── Sigmob Adapter 4.25.9                           │   │
│  │  ├── CSJ/Pangle Adapter 7.3.0.8                      │   │
│  │  └── Kuying/SDM Adapter 6.5.54                       │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

三、anythink_sdk 包详解(桥梁层)

packages/anythink_sdk-1.0.8/ 是一个本地 Flutter 插件包,本质上是原生 AnyThink SDK 的 Flutter 桥接。

3.1 通信方式

使用单个 MethodChannel('anythink_sdk') 通道,双向通信:

Flutter → Native: 通过 channel.invokeMethod() 调用原生方法
Native → Flutter: 通过 channel.setMethodCallHandler() 回调结果

3.2 Dart 端 API 结构

所有类集中在 at_index.dart 中导出:

全局实例

用途

ATFlutterChannel

AnythinkSdk

原始 MethodChannel 实例(仅 platformVersion

ATInitManger

ATInit

SDK 初始化、GDPR、渠道配置

ATListenerManager

ATListener

所有广告格式的回调分发器

ATSplashManager

ATSplash

开屏广告加载/展示/状态查询

ATBannerManager

ATBanner

横幅广告(未使用)

ATInterstitialManager

ATInterstitial

插屏广告(未使用)

ATRewardedManager

ATRewarded

激励视频(未使用)

ATNativeManager

ATNative

原生广告(未使用)

3.3 回调系统

ATListener 注册为 MethodChannelsetMethodCallHandler,解析 methodName 分发到对应的 StreamController

原生 invokeMethod("SplashCall", data)
→ ATListener._adMethodHandler()
→ 识别 "SplashCall"
→ splashEventController.add(ATSplashResponse.fromMap(data))
→ 订阅者(AdTakuSplash.initListener)收到事件

每个广告格式都有独立的响应模型:

响应类

状态枚举

事件数量

ATSplashResponse

SplashStatus

18 种(含广告源级别)

ATBannerResponse

BannerStatus

15 种

ATInterstitialResponse

InterstitialStatus

17 种

ATRewardResponse

RewardedStatus

23 种

ATNativeResponse

NativeStatus

21 种

ATInitResponse

-

GDPR/位置回调

ATDownloadResponse

DownloadStatus

仅 Android

3.4 平台视图

横幅广告和原生广告支持 AndroidView/UiKitView 嵌入 Flutter Widget 树:

  • PlatformBannerWidgetat_banner_platform_view
  • PlatformNativeWidgetat_native_platform_view

开屏广告使用平台视图,而是由原生端直接在 Activity 的 android.R.id.content 上覆盖 FrameLayout 渲染。


四、lib/ad/ 管理层详解

4.1 文件依赖关系

callback_ad_splash.dart           ← 纯数据类型,定义回调和事件模型
         ↑
  ad_taku_splash.dart             ← 开屏广告控制器(加载/展示/超时/释放)
         ↑
ad_taku_splash_widget.dart        ← Flutter Widget,UI 层桥接
         │
ad_taku_sdk.dart                  ← SDK 初始化(与 splash 无依赖)

4.2 AdTakuSdk — SDK 初始化

文件: lib/ad/ad_taku_sdk.dart

静态工具类,封装 ATInitManger 的方法:

  • 防重复初始化(_sdkInited 标志位)
  • setupAndInit():编排方法,依次配置日志/渠道 → 注册 GDPR 监听器 → 调用 initTaku()(即 ATInitManger.initAnyThinkSDK
  • GDPR 合规处理:获取用户位置 → 如在欧盟且未设置 → 弹出 GDPR 弹窗
  • 提供各种配置快捷方法(日志、渠道、自定义数据、设备信息限制)

4.3 AdTakuSplash — 开屏广告控制器

文件: lib/ad/ad_taku_splash.dart

单例模式,管理开屏广告完整生命周期:

核心机制:

load() → _loadOnceAndWait()
         ├── 防并发加载(_isLoading 标志)
         ├── 创建 Completer<bool>
         ├── 发起 ATSplashManager.loadSplash()(不 await,防 MethodChannel 死锁)
         ├── 等待 Completer(超时时间 = tolerateTimeoutMs + 1500ms)
         └── Completer 由事件监听器中的 splashDidFinishLoading/fail/timeout 完成
             → load() 返回 true/false

状态追踪:

  • _isLoading — 防止并发加载
  • _isShowing — 防止重复展示
  • _attemptSeq — 单调递增的尝试序号,用于日志关联
  • _lastAttemptEndByTimeout — 上次是否因超时结束
  • _lastStatus — 最近一次 SplashStatus

回调路由: initListener() 订阅 ATListenerManager.splashEventHandler,通过 SplashStatus 映射到 SplashCallbacks 中对应的回调。

4.4 AdTakuSplashWidget — 开屏 UI

文件: lib/ad/ad_taku_splash_widget.dart

StatefulWidget,开屏页面的实现:

  • 展示 Assets.images.imageSplash 作为背景图
  • _showAdSplashAsync()initStateaddPostFrameCallback 中执行
  • 容错机制(全部 fallthrough):
    • 无可用视图 → 直接进应用
    • 应用不在前台 → 直接进应用
    • SDK 未初始化 → 直接进应用
    • 广告加载失败 → 进应用
    • 广告展示失败 → 进应用
    • 广告超时 → 进应用
    • 广告被用户关闭 → 进应用
    • MissingPluginException → 进应用
  • _enterAppOnce() 通过 _hasEnteredApp 标志位确保只会进入应用一次
  • 进入目标:已注册用户 → NavigationPage;未注册 → LoginPage

4.5 SplashCallbacks — 事件回调模型

文件: lib/ad/callback/callback_ad_splash.dart

回调字段

触发条件

onFailToLoad

广告加载失败

onFinishLoading

广告加载完成(准备展示)

onTimeout

广告超时

onShowSuccess

广告展示成功

onShowFailed

广告展示失败

onClick

用户点击广告

onDeepLink

DeepLink 跳转

onWillClose

广告即将关闭

onClose

广告已关闭

onUnknown

未识别的状态


五、启动集成流程

文件: lib/startup/app_bootstrapper.dart

5.1 整体时序

main()
  │
  ▼
AppBootstrapper.bootstrap()
  │
  ├── ① _initBase()        — 竖屏锁定、状态栏、包信息
  ├── ② _initProviders()   — Riverpod 容器、Dio、网络
  ├── ③ _initEnvironment() — 读取 API 环境
  │
  ├── ④ _isAgreePrivacy()?
  │     NO  ──→ AppLauncherWidget(未同意隐私,跳过所有 SDK 初始化)
  │     YES ──→ 继续
  │
  ├── ⑤ initSDKAsync(initAdSDK: true)
  │     ├── 阿里一键登录 SDK
  │     ├── 微信 SDK
  │     ├── Flutter Foreground Task
  │     └── _initATT()          — iOS 请求 ATT 权限
  │           └── 授权后 ──→ _initAdSDK()
  │                 ├── 请求服务端: BasicsServer.getAdConfList()
  │                 │    参数: { platform: android/ios, adType: "taku" }
  │                 ├── 过滤: platform 匹配 + adType="splash" + isEnable=1
  │                 ├── 提取 appId, appKey
  │                 └── AdTakuSdk.setupAndInit(appId, appKey)
  │                       → ATInitManger.initAnyThinkSDK()
  │
  ├── ⑥ _resolveStartPage()
  │     ├── 刷新用户信息
  │     ├── 未登录 → LoginPage
  │     ├── 已登录:
  │     │   └── 无 ATT 权限 OR 无 adConf → NavigationPage/LoginPage
  │     └── 已登录 + ATT + 有 adConf → AdTakuSplashWidget
  │
  └── ⑦ runApp(startPage)

5.2 关键设计点

隐私优先: 用户未同意隐私协议时,完全不初始化广告 SDK,也不请求任何广告配置。

ATT 依赖(iOS 特有): iOS 上必须在用户授予 AppTrackingTransparency 权限后才会初始化广告 SDK。Android 上始终返回 true。

服务端驱动配置: 所有广告参数(appIdappKeyplacementIdisEnable)均从服务端下发,运营可远程控制开屏广告的展示。


六、服务端配置模型

模型字段

类型

说明

id

int

主键

platform

String

平台标识:android / ios

appId

String

Taku SDK 的应用 ID

appKey

String

Taku SDK 的应用 Key

adType

String

广告类型:splash

placementId

String

广告位 ID

isEnable

int

启用标志:0=禁用, 1=启用

source

String?

来源标识

请求接口: BasicsApi.adConfListPOST 请求,参数 platform(当前设备平台)和 adType: "taku"

过滤逻辑(app_bootstrapper.dart lines 197-201):

result.where((element) =>
    element.platform == platform &&
    element.adType == "splash" &&
    element.isEnable == 1
).toList();

七、原生平台集成要点

7.1 Android

  • Gradle 依赖: 通过插件的 build.gradle 声明 api("com.anythink.sdk:core-taku:6.5.70") 及多家适配器
  • AAR 文件: 插件内嵌 anythink_core.aaranythink_splash.aar
  • 开屏渲染: ATSplashHelper.java 创建 ATSplashAd,通过 activity.addContentView()FrameLayout 添加到 android.R.id.content
  • 聚合网络: Sigmob、CSJ/Pangle、Kuying/SDM 三家广告 SDK
  • 应用层 AndroidManifest: 无任何广告相关声明,全部由插件 Manifest 合并提供

7.2 iOS

  • 语言: 全部 Objective-C(未使用 Swift)
  • 架构: ATFAdManger 使用 Category 按广告格式拆分(+Initchannel+SplashAd+RewardedVideo 等)
  • 回调: ATFSendSignalManger 单例通过 [self.methodChannel invokeMethod:methodName arguments:arguments] 回传事件
  • ATT: 应用层在 _initATT() 中弹出 ATT 授权请求(iOS 14+)

八、架构特点与设计模式

8.1 设计模式总结

模式

应用位置

单例模式

AdTakuSplash.instanceAdTakuSdk(静态类)、ATInitMangerATSplashManager 等所有管理器

观察者模式

ATListener 使用 StreamController 分发事件,AdTakuSplash.initListener() 订阅

回调模式

SplashCallbacks 定义 10 种可选回调

编排模式

AdTakuSdk.setupAndInit() 编排日志→渠道→监听器→初始化的顺序

Promise/Future

_loadOnceAndWait() 使用 Completer<bool> + 超时实现异步等待

守卫模式

防重复初始化、防并发加载、防重复进入应用(_hasEnteredApp

8.2 容错设计

广告系统采用 "优雅降级" 原则:广告是增强体验,不是必要功能。

  • SDK 初始化失败 → 不展示广告,正常进入应用
  • 广告加载失败 → 立即进入应用
  • 广告展示失败/超时 → 立即进入应用
  • 用户关闭广告 → 进入应用
  • 插件未安装/未注册(MissingPluginException)→ 进入应用
  • 隐私未同意 → 不初始化 SDK,直接进入应用

8.3 安全与合规

  • 隐私协议前置: 用户不同意隐私协议不初始化任何广告 SDK
  • GDPR 支持: 提供 getUserLocation()(欧盟检测)和 showGDPRConsentDialog()(UMP 弹窗)
  • ATT 授权(iOS 14+): 需要用户授权后才能初始化广告 SDK
  • 设备信息限制: deniedUploadDeviceInfo() 可限制上报特定设备标识(OAID、IMEI 等)

8.4 可扩展性

虽然当前仅使用开屏广告,但 anythink_sdk 包完整支持:

  • 横幅广告(Banner)
  • 插屏广告(Interstitial,含自动加载模式)
  • 激励视频(Rewarded Video,含自动加载模式)
  • 原生广告(Native,含平台视图嵌入)

每种广告格式的 Flutter 端 API、原生端实现和回调模型均已就绪,可在服务端配置后快速启用。


九、数据流完整链路

用户打开 App
    │
    ▼
AppBootstrapper.bootstrap()
    │
    ├── 隐私同意?
    │     ├── NO  → AppLauncherWidget(停止)
    │     └── YES
    │
    ├── _initAdSDK()
    │     ├── HTTP 请求: BasicsApi.adConfList(platform, adType="taku")
    │     ├── 服务端返回: [{appId, appKey, adType: "splash", placementId, isEnable: 1}]
    │     ├── 过滤匹配项
    │     ├── AdTakuSdk.setupAndInit(appId, appKey)
    │     │     ├── ATInitManger.setLogEnabled()
    │     │     ├── ATInitManger.setChannelStr()
    │     │     ├── AdTakuSdk.addSDKListener()
    │     │     │     └── 监听 userLocation 事件 → 在欧盟 → showGDPRConsentDialog()
    │     │     └── ATInitManger.initAnyThinkSDK(appid, appkey)
    │     │           └── MethodChannel → 原生 ATAdInitManger → Taku Core 初始化
    │     └── 保存 adConfModel(含 placementId)
    │
    ├── _resolveStartPage()
    │     └── 已登录 + ATT + adConf → AdTakuSplashWidget(placementId)
    │
    ▼
AdTakuSplashWidget.initState()
    │
    ├── addPostFrameCallback → _showAdSplashAsync()
    │
    ▼
_showAdSplashAsync()
    │
    ├── 前置检查(视图/生命周期/SDK已初始化)
    │
    ├── AdTakuSplash.instance
    │     .on(failToLoad→_enterAppOnce, close→_enterAppOnce, ...)
    │     .setPlacementID(placementId)
    │     .showIfReadyOtherwiseLoad(autoShowAfterLoad: true)
    │
    ├── AdTakuSplash.initListener()
    │     └── ATListenerManager.splashEventHandler.listen()
    │
    ├── ATSplashManager.splashReady()?
    │     ├── YES → show()
    │     │     └── ATSplashManager.showSplash()
    │     │           └── MethodChannel → 原生展示(FrameLayout覆盖内容)
    │     │                 用户观看广告 → 点击/关闭 → 原生回调
    │     │                 → MethodChannel("SplashCall") → ATListener
    │     │                 → SplashStatus.splashDidClose
    │     │                 → SplashCallbacks.onClose → _enterAppOnce()
    │     │
    │     └── NO  → load()
    │           └── _loadOnceAndWait()
    │                 ├── 创建 Completer
    │                 ├── ATSplashManager.loadSplash(placementID, tolerateTimeout)
    │                 │     └── MethodChannel → 原生加载广告
    │                 └── 等待 Completer(超时 tolerateTimeoutMs + 1500ms)
    │                       │
    │                       原生加载完成 → MethodChannel("SplashCall")
    │                       → SplashStatus.splashDidFinishLoading
    │                       → _completeAttemptIfNeeded(true) → Completer.complete(true)
    │                       → load() 返回 true
    │                       → autoShow → show()
    │                            └── 同上方 YES 分支
    │
    └── 任意失败 → _enterAppOnce()
          └── CommonNavigator.pushAndRemoveUntil(NavigationPage/LoginPage)

十、关键文件索引

文件路径

职责

行数

lib/ad/ad_taku_sdk.dart

Taku SDK 初始化封装

~130

lib/ad/ad_taku_splash.dart

开屏广告控制器

~280

lib/ad/ad_taku_splash_widget.dart

开屏页面 UI

~100

lib/ad/callback/callback_ad_splash.dart

开屏事件回调模型

~35

lib/startup/app_bootstrapper.dart

启动编排(含广告初始化)

~230

packages/anythink_sdk-1.0.8/lib/at_splash.dart

Plugin 开屏 API

~55

packages/anythink_sdk-1.0.8/lib/at_init.dart

Plugin 初始化 API

~100

packages/anythink_sdk-1.0.8/lib/at_listener.dart

Plugin 事件分发器

~80

packages/anythink_sdk-1.0.8/lib/at_splash_response.dart

开屏回调数据模型

~65

packages/anythink_sdk-1.0.8/lib/at_index.dart

Plugin API 统一导出

~15

packages/anythink_sdk-1.0.8/android/.../splash/ATSplashHelper.java

Android 开屏原生实现

-

packages/anythink_sdk-1.0.8/ios/.../splash/ATFSplashAdManger.m

iOS 开屏原生实现

-

lib/modules/basics/model/mode_ad.dart

广告配置数据模型

~20

lib/modules/basics/server/server_basics.dart

广告配置 API 调用

~20


十一、验证方式

  1. 检查 SDK 初始化日志: 打开 SDK 调试日志(AdTakuSdk.setLogEnabled(true)),观察 [AdTakuSdk] 前缀的输出
  2. 验证服务端配置: 通过 /basics/adConfList 接口确认返回的 appId/appKey/placementId 是否正确
  3. 原生端调试: AnyThink 提供 ATInitManger.showDebuggerUI() 打开调试面板,可查看各广告平台的加载情况
  4. 开屏广告展示测试: 冷启动应用,观察开屏广告是否正常加载和展示,确认关闭后正确进入主页面
  5. 容错测试: 断网启动 → 确认不进广告直接进入主页面;关闭广告 SDK 配置 → 确认不进广告直接进入主页面
评论 0

支持 @用户名 提醒对方(需为站内已注册用户名);回复仅支持一层楼中楼。

登录后发表评论、回复与 @ 提及。

举报

举报会匿名发送给管理员审核。

  • 暂无评论,来发表第一条。

码谱 · The Digital Atelier · 技术内容社区