client_framework/docs/skin_app_development_guide.md
2026-03-26 10:39:39 +08:00

22 KiB
Raw Blame History

换皮应用开发完整流程指南

本文档说明如何使用 client_proxy_framework 从零创建并完成一个换皮应用。


重要说明:本指南仅完成数据框架的对接,包括:

  • API 请求封装与字段映射
  • 第三方 SDKAdjust、Facebook、Google Play集成
  • 用户认证与归因追踪

UI 组件需要另行对接,包括:

  • 首页、生成页、个人中心等业务页面
  • 主题配色、字体、图标等 UI 样式
  • 业务组件和交互逻辑

详见本文档最后一章 后续开发指引


目录

  1. 准备阶段:创建项目目录
  2. 填写配置模板
  3. 创建 Flutter 项目
  4. 集成框架
  5. 配置应用信息
  6. 配置第三方-sdk
  7. 实现业务代码
  8. 调试与测试
  9. 上线准备
  10. 后续开发指引

1. 准备阶段:创建项目目录

1.1 创建项目目录

在合适的位置创建项目目录:

# 请将 your_app_name 替换为实际的应用名称
mkdir your_app_name
cd your_app_name

1.2 复制配置模板

从框架目录复制配置模板到项目的 docs 目录下:

mkdir docs
cp ../client_proxy_framework/docs/new_app_config_template.md docs/
cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/

复制后的目录结构:

your_app_name/
├── docs/
│   ├── new_app_config_template.md    # 应用配置模板
│   └── sdk_integration_guide.md      # SDK 集成指南
└── ...

1.3 打开配置模板

使用编辑器打开配置模板文件:

# 使用 VSCode
code docs/new_app_config_template.md

# 或使用其他编辑器
open docs/new_app_config_template.md

1.4 下一步

请先完成配置模板的填写,再继续执行后续步骤。

配置模板包含以下信息:

  • 应用基本信息appId、packageName、AES 密钥等)
  • Adjust 配置App Token、Event Tokens
  • Facebook 配置App ID、Client Token
  • Google Play 配置(商品 ID

填写完成后,继续执行第 3 步。


2. 填写配置模板

请参考 docs/new_app_config_template.md 文件,填写以下配置信息:

2.1 应用基本信息

配置项 占位符 填写值
应用名称 填写应用名称
Android 包名 填写 Android 包名
iOS Bundle ID 填写 iOS Bundle ID
应用 IDappId 填写 appId

2.2 服务器配置

配置项 占位符 填写值
预发布环境 URL 填写预发布环境 URL
生产环境 URL 填写生产环境 URL
代理接口路径 填写代理接口路径

2.3 Adjust SDK 配置

配置项 占位符 填写值
App Token 填写 Adjust App Token
Environment sandbox / production

2.4 Facebook SDK 配置

配置项 占位符 填写值
App ID 填写 Facebook App ID
Client Token 填写 Client Token

2.5 AES 密钥

配置项 占位符 填写值
AES 密钥 填写 16 字符 AES-128 密钥

框架使用 AES-128-ECB 模式,密钥固定为 16 字符。

2.6 下一步

配置填写完成后,继续执行第 3 步。

如果配置复杂或有任何疑问,请将填写好的配置发送给我进行确认。


3. 创建 Flutter 项目

3.1 在项目目录下执行

确保你在之前创建的项目目录下执行:

# 如果不在项目目录下,先进入
cd your_app_name

3.2 创建 Flutter 项目

# 请将 com.yourcompany 替换为你的组织包名
flutter create --org com.yourcompany your_app_name
cd your_app_name

3.3 复制配置模板到项目

mkdir docs
cp ../client_proxy_framework/docs/new_app_config_template.md docs/
cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/

3.4 下一步

继续执行 第 4 步:集成框架


4. 集成框架

4.1 添加依赖

编辑 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  
  # 框架依赖
  client_proxy_framework:
    path: ../client_proxy_framework  # 或使用 pub 发布的版本
  
  # 框架需要的额外依赖
  http: ^1.2.2
  encrypt: ^5.0.3
  crypto: ^3.0.3
  logger: ^2.0.2
  shared_preferences: ^2.2.2
  device_info_plus: ^11.1.0

4.2 目录结构

建议按以下结构组织代码:

lib/
├── main.dart                    # 入口文件
├── app.dart                     # MaterialApp
├── core/
│   ├── config/
│   │   └── app_config.dart     # 应用配置类
│   ├── auth/
│   │   └── auth_service.dart   # 认证服务实现
│   ├── user/
│   │   └── user_state.dart     # 用户状态管理
│   └── theme/
│       ├── app_colors.dart     # 主题配色
│       ├── app_typography.dart # 字体样式
│       └── app_spacing.dart    # 间距常量
├── features/
│   ├── home/                   # 首页模块
│   ├── create/                 # 生成页模块
│   ├── profile/                # 个人中心模块
│   └── recharge/               # 充值页模块
└── shared/
    ├── tab_selector_scope.dart  # Tab 导航作用域
    └── widgets/                # 共享组件

5. 配置应用信息

5.1 创建应用配置类

创建 lib/core/config/app_config.dart

import 'package:client_proxy_framework/client_proxy_framework.dart';

class YourAppConfig implements AppConfig {
  @override
  String get appId => 'your_app_id';

  @override
  String get packageName => 'com.yourcompany.yourapp';

  @override
  String get aesKey => 'your_16_char_key';  // AES-128 需要 16 字符

  @override
  String get preBaseUrl => 'https://pre-api.example.com';

  @override
  String get prodBaseUrl => 'https://api.example.com';

  @override
  String get proxyPath => '/v1/proxy';

  @override
  FieldMapping? get fieldMapping => DefaultFieldMapping.instance;
}

5.2 创建用户状态管理

创建 lib/core/user/user_state.dart

import 'package:flutter/foundation.dart';

class UserState {
  static String? _userId;
  static int _credits = 0;
  static String? _avatar;
  static String? _userName;
  static String? _countryCode;

  static String? get userId => _userId;
  static int get credits => _credits;
  static String? get avatar => _avatar;
  static String? get userName => _userName;
  static String? get countryCode => _countryCode;

  static void setUserId(String id) => _userId = id;
  static void setCredits(int credits) => _credits = credits;
  static void setAvatar(String avatar) => _avatar = avatar;
  static void setUserName(String name) => _userName = name;
  static void setCountryCode(String code) => _countryCode = code;
}

6. 配置第三方 SDK

6.1 填写配置信息

参考 SDK 集成配置指南,获取并填写以下信息:

SDK 必需信息
Adjust App Token, Environment
Facebook App ID, Client Token
Google Play 无(商品 ID 从后端获取)

6.2 Android 配置

6.2.1 创建 strings.xml

创建 android/app/src/main/res/values/strings.xml

<resources>
    <string name="facebook_app_id">your_facebook_app_id</string>
    <string name="facebook_client_token">your_facebook_client_token</string>
</resources>

6.2.2 更新 AndroidManifest.xml

<application> 标签之前添加:

<!-- Adjust -->
<meta-data
    android:name="com.adjust.sdk.appToken"
    android:value="your_adjust_app_token" />
<meta-data
    android:name="com.adjust.sdk.environment"
    android:value="sandbox" />

<!-- Facebook -->
<meta-data
    android:name="com.facebook.sdk.ApplicationId"
    android:value="@string/facebook_app_id" />
<meta-data
    android:name="com.facebook.sdk.ClientToken"
    android:value="@string/facebook_client_token" />

6.2.3 创建 Application 类

创建 android/app/src/main/kotlin/com/yourcompany/yourapp/App.kt

package com.yourcompany.yourapp

import android.app.Application
import android.os.Handler
import android.os.Looper
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel

class App : Application() {
    companion object {
        const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk"
        const val ENGINE_ID = "main"
    }

    override fun onCreate() {
        super.onCreate()
        
        FacebookSdk.sdkInitialize(this)
        FacebookSdk.setIsDebugEnabled(true)
        FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
        
        Handler(Looper.getMainLooper()).postDelayed({
            val engine = FlutterEngineCache.getInstance().get(ENGINE_ID)
            if (engine != null) {
                MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL)
                    .invokeMethod("onFacebookSdkInitialized", null)
            }
        }, 100)
    }
}

6.2.4 创建 MainActivity

创建 android/app/src/main/kotlin/com/yourcompany/yourapp/MainActivity.kt

package com.yourcompany.yourapp

import android.os.Handler
import android.os.Looper
import com.facebook.appevents.AppEventsLogger
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    companion object {
        const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk"
        const val ENGINE_ID = "main"
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        AppEventsLogger.activateApp(application)
        
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "waitForFacebookSdkInit" -> {
                        AppEventsLogger.activateApp(application)
                        result.success(true)  // 必须调用 result.success() 否则 Dart 端会卡住
                    }
                    "onFacebookSdkInitialized" -> {
                        result.success(true)
                    }
                    else -> {
                        result.notImplemented()
                    }
                }
            }
        
        Handler(Looper.getMainLooper()).postDelayed({
            FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
        }, 100)
    }
}

注意result.success() 必须被调用,否则 Dart 端的 await 会一直等待响应,导致应用卡住。

6.2.5 更新 AndroidManifest.xml 使用 Application

<application
    android:name=".App"
    android:label="Your App Name"
    android:icon="@mipmap/ic_launcher">

6.3 iOS 配置

6.3.1 更新 Info.plist

ios/Runner/Info.plist 中添加:

<key>AdjustAppToken</key>
<string>your_adjust_app_token</string>
<key>AdjustEnvironment</key>
<string>sandbox</string>

<key>FacebookAppID</key>
<string>your_facebook_app_id</string>
<key>FacebookClientToken</key>
<string>your_facebook_client_token</string>
<key>FacebookDisplayName</key>
<string>Your App Name</string>

7. 实现业务代码

7.1 创建认证服务实现

创建 lib/core/auth/auth_service.dart

import 'dart:convert';
import 'package:client_proxy_framework/client_proxy_framework.dart';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';

import '../user/user_state.dart';

/// 归因回调实现
class AppAttributionCallbacks implements AttributionCallbacks {
  @override
  Future<String?> getReferrer() async {
    final attribution = AnalyticsService.getAttribution();
    if (attribution != null) {
      return _attributionToJson(attribution);
    }
    return '';
  }

  String _attributionToJson(AttributionData data) {
    final map = <String, dynamic>{
      'trackerToken': data.trackerToken,
      'trackerName': data.trackerName,
      'network': data.network,
      'campaign': data.campaign,
      'adgroup': data.adgroup,
      'creative': data.creative,
      'clickLabel': data.clickLabel,
      'costType': data.costType,
      'costAmount': _jsonEncodableCostAmount(data.costAmount),
      'costCurrency': data.costCurrency,
      'jsonResponse': data.jsonResponse,
      'fbInstallReferrer': data.fbInstallReferrer,
    };
    return base64Encode(utf8.encode(jsonEncode(map)));
  }

  Object? _jsonEncodableCostAmount(double? v) {
    if (v == null) return null;
    if (v.isNaN || !v.isFinite) return v.toString();
    return v;
  }

  @override
  Future<String?> getAdjustReferrer() async => getReferrer();

  @override
  Future<String?> getPlatformReferrer() async => null;
}

/// 认证回调实现
class AppAuthCallbacks implements AuthServiceCallbacks {
  @override
  Future<String> getDeviceId() async {
    final deviceInfo = DeviceInfoPlugin();
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        final android = await deviceInfo.androidInfo;
        return android.id;
      case TargetPlatform.iOS:
        final ios = await deviceInfo.iosInfo;
        return ios.identifierForVendor ?? 'ios-unknown';
      default:
        return 'device-${DateTime.now().millisecondsSinceEpoch}';
    }
  }

  @override
  String computeSign(String deviceId) {
    return md5.convert(utf8.encode(deviceId)).toString().toUpperCase();
  }

  @override
  void onLoginSuccess(FastLoginResponse data) {
    if (data.userId != null) UserState.setUserId(data.userId!);
    if (data.credits != null) UserState.setCredits(data.credits!);
    if (data.avatar != null) UserState.setAvatar(data.avatar!);
    if (data.userName != null) UserState.setUserName(data.userName!);
  }

  @override
  void onCommonInfoLoaded(CommonInfoResponse data) {
    if (data.credits != null) UserState.setCredits(data.credits!);
    if (data.avatar != null) UserState.setAvatar(data.avatar!);
    if (data.userName != null) UserState.setUserName(data.userName!);
  }

  @override
  void onLoginFailed(String msg) {
    debugPrint('[AuthService] Login failed: $msg');
  }
}

/// 认证服务
class AuthService {
  static final _authCallbacks = AppAuthCallbacks();
  static final _attributionCallbacks = AppAttributionCallbacks();

  static Future<void> init() async {
    AttributionService.init(_attributionCallbacks);
    FrameworkAuthService.init(_authCallbacks);
    await FrameworkAuthService.start();
  }

  static Future<void> get loginComplete => FrameworkAuthService.loginComplete;
}

7.2 创建 main.dart

创建 lib/main.dart

import 'package:flutter/material.dart';
import 'package:client_proxy_framework/client_proxy_framework.dart';

import 'app.dart';
import 'core/auth/auth_service.dart';
import 'core/config/app_config.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. 初始化 API 客户端
  ApiClient.init(YourAppConfig());

  // 2. 初始化分析服务
  await AnalyticsService.init(
    AnalyticsConfig(
      packageName: 'com.yourcompany.yourapp',  // 包名MethodChannel 使用
      adjustConfig: AdjustConfig(
        appToken: 'your_adjust_app_token',
        environment: AdjustEnv.sandbox,  // 上线改为 production
      ),
      facebookConfig: FacebookConfig(
        appId: 'your_facebook_app_id',
        clientToken: 'your_facebook_client_token',
      ),
    ),
  );

  // 3. 提前获取归因
  await AnalyticsService.initAttribution();

  // 4. 启动应用
  runApp(const App());

  // 5. 初始化认证
  AuthService.init();
}

7.3 创建 App 组件

创建 lib/app.dart

import 'package:flutter/material.dart';

import 'home_screen.dart';

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Your App Name',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

8. 调试与测试

8.1 运行调试版本

flutter run

8.2 检查日志

运行时应看到以下日志:

D/Adjust  (xxxxx): Adjust SDK started
I/flutter (xxxxx): [AuthService] start: deviceId=xxx
I/flutter (xxxxx): Facebook App Events initialized (from native callback)

8.3 常见问题排查

问题 解决方案
Adjust 日志显示 "SANDBOX" 正常,测试环境会显示
Facebook 报错 "must be initialized" 检查 AndroidManifest.xml 和 Application 类配置
归因未上报 检查 initAttribution() 是否在 main() 中调用

9. 上线准备

9.1 修改环境配置

main.dart

// Adjust 改为 production
adjustConfig: AdjustConfig(
  appToken: 'your_production_app_token',
  environment: AdjustEnv.production,  // 修改这里
),

// Facebook 关闭调试日志
facebookConfig: FacebookConfig(
  appId: 'your_facebook_app_id',
  clientToken: 'your_facebook_client_token',
  debugLogs: false,  // 关闭调试日志
),

Android

<!-- AndroidManifest.xml -->
<meta-data
    android:name="com.adjust.sdk.environment"
    android:value="production" />

iOS

<!-- Info.plist -->
<key>AdjustEnvironment</key>
<string>production</string>

9.2 构建发布版本

# Android
flutter build apk --release
flutter build appbundle --release

# iOS
flutter build ios --release

9.3 检查清单

  • Adjust App Token 已替换为生产环境
  • Adjust Environment 已改为 production
  • Facebook 调试日志已关闭
  • Facebook Client Token 已更新(生产环境)
  • 应用名称和包名正确
  • 所有第三方 SDK 配置完成

附录:框架 API 快速参考

分析服务

// 初始化
await AnalyticsService.init(config);

// 埋点
AnalyticsService.trackEvent('event_token');
AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD');
AnalyticsService.trackRegister();
AnalyticsService.trackSubscribe('monthly_vip');

// 获取归因
final attribution = AnalyticsService.getAttribution();

认证服务

// 获取登录完成状态
await AuthService.loginComplete;

// 发起登录
await AuthService.init();

API 请求

// 获取代理客户端
final proxy = ApiClient.instance.proxy;

// GET 请求
final res = await proxy.request(
  path: '/v1/endpoint',
  method: 'GET',
);

// POST 请求
final res = await proxy.request(
  path: '/v1/endpoint',
  method: 'POST',
  body: {'key': 'value'},
);

// 请求带字段映射的实体
final res = await proxy.requestEntity(
  path: '/v1/entity',
  method: 'GET',
  entityFactory: YourEntity.fromJson,
);

10. 后续开发指引

完成本指南后,数据框架已就绪。接下来需要开发 UI 业务层

10.1 框架提供的 API 服务

服务类 用途
ImageApi 图片/视频生成相关 API
PaymentApi 支付相关 API
FeedbackApi 举报/反馈相关 API
UserApi 用户账户相关 API

10.2 框架提供的实体类

参考 client_proxy_framework/lib/src/entities/ 目录:

实体类 用途
UserEntities 用户、登录、通用信息响应
ImageEntities 分类、任务、生成结果
PaymentEntities 商品、订单、支付结果
FeedbackEntities 举报反馈相关

10.3 典型的 UI 开发流程

// 1. 在业务页面中使用框架 API
class HomeScreen extends StatefulWidget {
  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    // 等待登录完成(如果需要)
    await AuthService.loginComplete;
    
    // 调用框架 API 获取数据
    final res = await ImageApi.getCategoryList();
    if (res.isSuccess) {
      // 处理数据...
    }
  }
}

// 2. 使用 UserState 管理状态
UserState.setCredits(credits);
UserState.setUserId(userId);

// 3. 调用分析服务埋点
AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD');
AnalyticsService.trackEvent('your_event_token');

10.4 建议的 UI 开发顺序

  1. 首页HomeScreen - 展示分类和任务卡片
  2. 生成页CreateScreen - 创建生成任务
  3. 个人中心ProfileScreen - 用户信息和设置
  4. 充值页RechargeScreen - 内购支付流程

10.5 参考实现

参考 app_client_1 项目中的 UI 实现:

  • lib/features/home/home_screen.dart - 首页实现
  • lib/shared/widgets/ - 共享组件(按钮、卡片等)
  • lib/core/theme/ - 主题配置