22 KiB
换皮应用开发完整流程指南
本文档说明如何使用 client_proxy_framework 从零创建并完成一个换皮应用。
推荐优先阅读:《创建新换皮应用 — 步骤清单》 — 与当前框架实现(JSON 配置、
ClientBootstrap、固定 Facebook Channel、默认归因回调等)保持同步的精简步骤。
getDeviceId/computeSign(与 FunyMee、app_client 对齐):device_id_and_sign.md
重要说明:本指南仅完成数据框架的对接,包括:
- API 请求封装与字段映射
- 第三方 SDK(Adjust、Facebook、Google Play)集成
- 用户认证与归因追踪
UI 组件需要另行对接,包括:
- 首页、生成页、个人中心等业务页面
- 主题配色、字体、图标等 UI 样式
- 业务组件和交互逻辑
详见本文档最后一章 后续开发指引。
目录
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 |
|
| 应用 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 在项目目录下执行
确保你在之前创建的项目目录下执行:
# 如果不在项目目录下,先进入
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 |
| 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. 实现业务代码
认证回调中的 getDeviceId、computeSign 与后端 fast_login 强相关;与 FunyMee / app_client 一致的字段含义、依赖与兜底策略见框架文档 device_id_and_sign.md(下文示例侧重代码结构,生产环境请以该规范为准)。
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 开发顺序
- 首页(HomeScreen) - 展示分类和任务卡片
- 生成页(CreateScreen) - 创建生成任务
- 个人中心(ProfileScreen) - 用户信息和设置
- 充值页(RechargeScreen) - 内购支付流程
10.5 参考实现
参考 app_client_1 项目中的 UI 实现:
lib/features/home/home_screen.dart- 首页实现lib/shared/widgets/- 共享组件(按钮、卡片等)lib/core/theme/- 主题配置