# 换皮应用开发完整流程指南
本文档说明如何使用 `client_proxy_framework` 从零创建并完成一个换皮应用。
> **推荐优先阅读**:[《创建新换皮应用 — 步骤清单》](create_new_skin_app.md) — 与当前框架实现(JSON 配置、`ClientBootstrap`、固定 Facebook Channel、默认归因回调等)保持同步的精简步骤。
>
> **`getDeviceId` / `computeSign`(与 FunyMee、app_client 对齐)**:[device_id_and_sign.md](device_id_and_sign.md)
***
> **重要说明**:本指南**仅完成数据框架的对接**,包括:
>
> - API 请求封装与字段映射
> - 第三方 SDK(Adjust、Facebook、Google Play)集成
> - 用户认证与归因追踪
>
> **UI 组件需要另行对接**,包括:
>
> - 首页、生成页、个人中心等业务页面
> - 主题配色、字体、图标等 UI 样式
> - 业务组件和交互逻辑
>
> 详见本文档最后一章 [后续开发指引](#后续开发指引)。
***
## 目录
1. [准备阶段:创建项目目录](#1-准备阶段创建项目目录)
2. [填写配置模板](#2-填写配置模板)
3. [创建 Flutter 项目](#3-创建-flutter-项目)
4. [集成框架](#4-集成框架)
5. [配置应用信息](#5-配置应用信息)
6. [配置第三方-sdk](#6-配置第三方-sdk)
7. [实现业务代码](#7-实现业务代码)
8. [调试与测试](#8-调试与测试)
9. [上线准备](#9-上线准备)
10. [后续开发指引](#10-后续开发指引)
***
## 1. 准备阶段:创建项目目录
### 1.1 创建项目目录
在合适的位置创建项目目录:
```bash
# 请将 your_app_name 替换为实际的应用名称
mkdir your_app_name
cd your_app_name
```
### 1.2 复制配置模板
从框架目录复制配置模板到项目的 `docs` 目录下:
```bash
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 打开配置模板
使用编辑器打开配置模板文件:
```bash
# 使用 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` | |
| 应用 ID(appId) | `填写 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 在项目目录下执行
确保你在之前创建的项目目录下执行:
```bash
# 如果不在项目目录下,先进入
cd your_app_name
```
### 3.2 创建 Flutter 项目
```bash
# 请将 com.yourcompany 替换为你的组织包名
flutter create --org com.yourcompany your_app_name
cd your_app_name
```
### 3.3 复制配置模板到项目
```bash
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. 集成框架
### 4.1 添加依赖
编辑 `pubspec.yaml`:
```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`:
```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`:
```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_integration_guide.md),获取并填写以下信息:
| 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`:
```xml
your_facebook_app_id
your_facebook_client_token
```
#### 6.2.2 更新 AndroidManifest.xml
在 `` 标签之前添加:
```xml
```
#### 6.2.3 创建 Application 类
创建 `android/app/src/main/kotlin/com/yourcompany/yourapp/App.kt`:
```kotlin
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`:
```kotlin
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
```xml
```
### 6.3 iOS 配置
#### 6.3.1 更新 Info.plist
在 `ios/Runner/Info.plist` 中添加:
```xml
AdjustAppToken
your_adjust_app_token
AdjustEnvironment
sandbox
FacebookAppID
your_facebook_app_id
FacebookClientToken
your_facebook_client_token
FacebookDisplayName
Your App Name
```
***
## 7. 实现业务代码
认证回调中的 **`getDeviceId`**、**`computeSign`** 与后端 **`fast_login`** 强相关;**与 FunyMee / app_client 一致的字段含义、依赖与兜底策略**见框架文档 [device_id_and_sign.md](device_id_and_sign.md)(下文示例侧重代码结构,生产环境请以该规范为准)。
### 7.1 创建认证服务实现
创建 `lib/core/auth/auth_service.dart`:
```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 getReferrer() async {
final attribution = AnalyticsService.getAttribution();
if (attribution != null) {
return _attributionToJson(attribution);
}
return '';
}
String _attributionToJson(AttributionData data) {
final map = {
'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 getAdjustReferrer() async => getReferrer();
@override
Future getPlatformReferrer() async => null;
}
/// 认证回调实现
class AppAuthCallbacks implements AuthServiceCallbacks {
@override
Future 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 init() async {
AttributionService.init(_attributionCallbacks);
FrameworkAuthService.init(_authCallbacks);
await FrameworkAuthService.start();
}
static Future get loginComplete => FrameworkAuthService.loginComplete;
}
```
### 7.2 创建 main.dart
创建 `lib/main.dart`:
```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`:
```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 运行调试版本
```bash
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
```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
```xml
```
#### iOS
```xml
AdjustEnvironment
production
```
### 9.2 构建发布版本
```bash
# 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 快速参考
### 分析服务
```dart
// 初始化
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();
```
### 认证服务
```dart
// 获取登录完成状态
await AuthService.loginComplete;
// 发起登录
await AuthService.init();
```
### API 请求
```dart
// 获取代理客户端
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 开发流程
```dart
// 1. 在业务页面中使用框架 API
class HomeScreen extends StatefulWidget {
@override
void initState() {
super.initState();
_loadData();
}
Future _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/` - 主题配置