开发环境依赖:
Java: 1.8Gradle: 7.2minSdkVersion: 17targetSdkVersion: 26
需要注意: 游戏开发和SDK接入是完全解耦的, 也就是发行设计SDK无需关心接入的游戏任何信息
这里具体的平台主要针对海外发行渠道, 所以默认是以 Google Play 作为上架平台来说明, 具体平台方向:
- 主流移动端应用市场(App Store/Google Play/Facebook)
- 官方渠道(比如官网, 一般情况下是提供安卓APK包和PC端的直接下载方式, H5则是提供游戏链接)
- 其他手游联运渠道(比如Huawei AppGallery/Samsung Store/Amazon Appstore/QooApp等商店渠道)
- 主流PC游戏市场(Steam/XBoxGame)
项目自定义的安卓发行包的包名:
com.game.fgame(可以自己按照习惯去自定义包名)
这里模拟假设以下信息方便我们搭建自己的发行平台:
- 公司名:
FastGame - 公司主体:
快游Game - 应用包:
com.game.fgame - 项目简称:
fgame - 项目目录:
fgame-sdk
具体的项目架构如下:
/ ─── fgame-lib/
│ ├─ src/
│ │ ├─ main/
│ │ │ ├─ java/com/game/fgame/
│ │ │ │ ├─ core/ // 核心逻辑(单例管理、初始化)
│ │ │ │ ├─ login/ // 登录模块
│ │ │ │ ├─ pay/ // 支付模块
│ │ │ │ ├─ report/ // 数据上报模块
│ │ │ │ ├─ config/ // 配置管理
│ │ │ │ ├─ utils/ // 工具类(加密、日志、网络等)
│ │ │ │ └─ FGameSdk.java // 对外接口类(单例,提供所有API)
│ │ │ ├─ res/ // 资源文件(若有自定义布局、图片等)
│ │ │ └─ AndroidManifest.xml // 声明权限、服务等(需注意与CP方合并冲突)
│ │ └─ test/ // 单元测试(核心工具类、签名逻辑等)
│ ├─ build.gradle // 库配置(依赖、混淆、AAR输出等)
│ └─ proguard-rules.pro // 混淆规则(保护SDK代码不被反编译)
│
│── gradle/ // gradle-wrapper 配置目录
|
|── build.gradle // 根目录打包配置
|
|── settings.gradle // 包含项目的模块设置, 如指定项目名称|要包含的子项目列表等
|
|── gradle.properties // 用于配置 Gradle 构建系统的属性文件, 如设置 Gradle 的堆大小|缓存等
这部分目录结构都是可以按照需求微调的, 具体是看主要功能模块的划分, 这部分目录可以先创建好.
项目初始化
推荐采用
Android Studio开发, 毕竟是谷歌专门的 IDE
在关于这部分开始之前, 需要配置 gradle 相关设置, 首先创建以下目录:
# 我编写都是 Linux 环境, 所以采用 shell 命令去创建
# 这里必须采用 gradle-wrapper 去锁定版本, 否则 gradle 所有版本或大或小有兼容性问题, 是很垃圾的管理工具
mkdir gradle/wrapper
# 生成第三方的镜像配置文件
touch gradle/wrapper/gradle-wrapper.properties
gradle-wrapper.properties 内容如下:
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v7.2.0/gradle-7.2-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
不要去追求高版本, 因为你对接的游戏CP方可能是二次开发之类没办法升级到太高版本的游戏, 而我们作为发行方应该去妥协游戏CP方.
另外这里需要注意, 如果作为发行方来说, 实际存在以下渠道包:
- 发行自己渠道生成包: 不需要依赖其他 Google/Apple 等第三方服务
- 另外发行的渠道包: 接入我们的SDK的额外 Google 之类第三方渠道包
所以商务联系游戏 CP 的时候要确定 CP 方面是要我们发行作为游戏联运, 还是作为代理上架平台去发行游戏类型:
| 合作模式 | 对应渠道包类型 | 核心特征 | 发行方角色 |
|---|---|---|---|
| 联运合作 | 发行自己渠道生成包 | 无需依赖Google/Apple等第三方服务,用发行方自有账号体系、支付通道、数据后台 | 提供“渠道+运营”支持,CP保留核心版权 |
| 代理上架(代发行) | 接入第三方渠道的发行包 | 需集成Google Play/Apple App Store等官方SDK(如Google登录、IAP支付),按平台规则上架 | 替CP完成“平台适配+合规上架”,CP专注研发 |
我们先从
联运合作方面设计, 代理上架这部分要涉及到第三方SDK接入这方面, 暂时不好做演示
这里需要先配置下 settings.gradle 用于声明内部的 lib 库信息, 有时候有些全局共享常量可以放置在其中:
rootProject.name = 'fgame-sdk' // 根目录的名称
// 子模块依赖
include ':fgame-lib'
这里最好把常用版本和加密信息编写在 gradle.properties 之中, 然后在内部引用:
# gradle 全局属性
# 安卓打包工具版本, 注意打包版本需要做好 gradle 匹配, 否则会出问题
AndroidBuildGradleToolsVersion=7.0.4
# 其他公共打包配置项常量, 注: CompileSdkVersion 低于30默认启动弹出 "该应用专为旧版本安卓构建"
AndroidMinSdkVersion=21
AndroidCompileSdkVersion=30
AndroidTargetSdkVersion=30
# SDK包的命名空间
GameSdkNamespace=com.game.fgame.core
而 build.gradle 就是我们引入共享配置的搭配设置:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
jcenter() // 保持对旧库的兼容性
}
dependencies {
// 加载全局常量的安卓打包版本
//noinspection AndroidGradlePluginVersion
classpath "com.android.tools.build:gradle:${AndroidBuildGradleToolsVersion}"
// 注意:不要在此处放置应用依赖项,它们属于各自的模块构建文件
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
jcenter() // 保持对旧库的兼容性
}
}
tasks.register('clean', Delete) {
delete rootProject.buildDir
}
上面的第三方镜像配置可以随便换成国内任何一家, 如果走代理不想更改可以直接删除
至此准备工作就做好了, 差不多就要开始打包编写 aar 的具体流程.
AAR 打包
这里可以简单理解 aar 打包就是类似生成 jar 的独有安卓打包方式,
我们编写的发行 SDK 就是基于这种方式来生成并且让 CP 方引入来生成 母包.
我们先在 fgame-lib 子目录下生成个简单的 aar 空包, build.gradle 内容如下:
apply plugin: 'com.android.library'
/**
* 获取正式包时间格式
* @return
*/
static def GetReleaseTime() {
return new Date().format("yyyyMMddHHmm", TimeZone.getTimeZone("GMT+8"))
}
/**
* 安卓打包配置
*/
android {
// 主要配置
compileSdkVersion AndroidCompileSdkVersion
// 包的命名空间
namespace GameSdkNamespace
// 默认配置
defaultConfig {
minSdkVersion AndroidMinSdkVersion
targetSdkVersion AndroidTargetSdkVersion
}
// 源代码引入结构
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
assets.srcDirs = ['src/main/assets']
res.srcDirs = ['src/main/res']
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/main/renderscript']
jniLibs.srcDir 'src/main/jniLibs'
}
}
// 解决lint报错的代码, 保持旧包兼容性
lintOptions {
quiet true
abortOnError false
ignoreWarnings true
checkReleaseBuilds false //方法过时警告的开关
disable 'InvalidPackage' //Some libraries have issues with this.
disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta.
disable 'IconDensities' //For testing purpose. This is safe to remove.
disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove.
}
// 打包配置, 主要排除掉对于说明文件
packagingOptions {
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/notice.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/dependencies.txt'
exclude 'META-INF/LGPL2.1'
exclude 'META-INF/versions/9/module-info.class'
}
// 打包构建配置
buildTypes {
release {
minifyEnabled true
zipAlignEnabled true
shrinkResources false // 建议在release版本设为true以减小体积
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard_common_rules.pro',
'proguard_rules_sdk_core.pro'
consumerProguardFiles 'proguard_rules_sdk_core.pro'
libraryVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "fgame_lib_v1-${GetReleaseTime()}.aar"
}
}
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard_common_rules.pro',
'proguard_rules_sdk_core.pro'
consumerProguardFiles 'proguard_rules_sdk_core.pro'
}
}
}
/**
* 第三方包配置
*/
dependencies {
// 引入 jar 和 aar 依赖包
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
// SDK所需基础依赖
compileOnly 'com.android.support:support-compat:28.0.0'
compileOnly 'com.android.support:support-core-utils:28.0.0'
compileOnly 'com.android.support:support-core-ui:28.0.0'
api 'com.android.support:support-v4:28.0.0'
compileOnly 'com.android.support:appcompat-v7:28.0.0'
compileOnly 'com.android.support:localbroadcastmanager:28.0.0'
compileOnly 'com.android.support:design:28.0.0'
}
然后就到了 gradle 最垃圾的调整不同版本兼容性的时候; 直到目前的 SDK 版本基本在 Android 21~30 徘徊,
甚至如果不是因为强制老版本弹出 "专为旧版本安卓打造", 可能SDK一直都是停留在 Android 16~28.
“专为旧版本安卓打包” 提示只有在 targetSdkVersion ≥ 28 消除
而且大问题就是可能游戏CP是没办法控制 Android 版本, 甚至游戏本体是基于加密 aar 的包体引入,
强制要求必须锁定在 Android 版本不能做大更新版本.
而且对于 JDK 版本也是有着强烈的依赖性, 在国内大部分都是基于 JDK1.8, 而目前新版本安卓的最低特性是 JDK11.
所以这里别看动手构建时候很快但后续大头全在调整这部分兼容性上, 这里目前会出现无法打出 aar 情况就是因为出现兼容性问题.
CP 流程
这里就是围绕CP方拿到 sdk.aar 之后需要做的流程, 首先可以建立个新的空 Android 项目,
内部项目需要创建 /libs(核心依赖库目录) 和 /assert/sdkcfg.xml(参数配置文件).
而因为 Android 版本大变迁过, 所以导致基础库版本:
Android: 老版本库,com.android.support.*包名之下管理AndoridX: 新版本库,androidx.*包名之下管理
注意这两者的接口是完全不兼容且不可以共存, 如果CP方面项目是新立项就尽量采用 AndroidX.
AndroidX 最新版本
最新版本的大变动SDK基础库版本
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
//sdk需要依赖以下android库,请务必导入,如果版本号与您本地有差异,请设置成大于或等于sdk所需库版本
implementation('androidx.legacy:legacy-support-v4:1.0.0')
implementation('androidx.multidex:multidex:2.0.1')
}
Android 传统版本
Android 老的常规版本库, 也就是老板需要引入的基础类库
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
//sdk需要依赖以下android库,请务必导入,如果版本号与您本地有差异,请设置成大于或等于sdk所需库版本
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:multidex:1.0.3'
}
上面就是CP拿到项目之后需要搭建框架项目, 需要把我们发行的 sdk.aar 最后放置在 libs 目录之中.
如果要做海外 Google|Facebook|Firebase 第三方接入, 必须采用
AndroidX版本(强依赖), AndroidSDK要尽可能最新版本
所以这部分思路其实看上去很简单, 但是最大的问题在于兼容性问题很大, 不止 gradle|abi 兼容还有CP项目的兼容.
CPS归因
在发行过程中细分 CPS(按效果付费)渠道时, 分包核心目标是精准追踪各渠道的转化数据(如注册|付费), 避免渠道包冲突和维护成本过高.
也就是对同一个发行游戏包可能会细分某些子渠道包, 分发游戏主播或者推广人员用来统计买量的推广效果, 可以简单视为
渠道分包
需要区分 渠道分包 其实有几种意义:
- 第三方平台的渠道分包: 比如微信|抖音|快手|Google|Facebook|不同商城游戏中心之类第三方渠道
- 联运游戏推广渠道分包: 发行方对推广人员后台分出专属游戏游戏, 用来评判和考核不同推广人员的绩效
- 游戏买量成效渠道分包: 对专门买量渠道构建游戏 apk 包, 比如抖音广告点击下载游戏包方便统计买量绩效
而渠道分包的核心原则:
- 唯一标识原则: 每个
CPS渠道必须有唯一标识(如渠道号), 确保数据上报时能精准归因 - 最小改动原则: 分包逻辑应与主
SDK解耦, 避免频繁修改核心代码导致兼容性问题 - 灵活扩展原则: 支持动态新增渠道, 无需重新编译 SDK, 降低运营成本
- 防作弊原则: 渠道标识需具备一定安全性, 防止被篡改伪造数据
目前主流的都是有推广后台平台提供给渠道推广主管登录, 在最高管理后台创建渠道指定的推广主管, 并且允许让他创建旗下对应的渠道人员并分配渠道ID.
而且安卓客户端对于 CPS 包目前主流方法有以下方式:
配置文件动态区分: 通过在SDK预留渠道配置接口加载渠道配置(无需修改任何)Gradle的打包: 利用安卓的productFlavors硬编码打出不同渠道包(每个渠道都要打包一次)动态参数注入:H5微端启动时候从URL截取渠道参数, 然后写入内部配置加载读取(启动参数,如 URL|IntentExtra 传入渠道码)
这里需要分多种情况来选择, 如果是 H5 嵌入运行的微端游戏, 那么很明显就是依赖游戏端传入参数;
实际上会有存在参数丢失问题, 而且是比较频繁的问题, 所以这里不深入讨论这种打包方式.
gradle本地打包配置则适合之前提出的代发行游戏渠道, 支持渠道专属资源(如图标|权限), 打包过程自动化方便定制- 配置参数区分则是只需要通过脚本解压修改内部替换内部配置文件内容, 实现直接打包生成
apk给对应渠道人员所属apk
如果是 CPS 归因的分包, 强烈推荐采用配置文件加载这种方式, 这种在多次实践中表明是最稳妥跑通流程方式, 实现步骤如下:
// FGameSdk.java 初始化方法
public static void init(Context context, String configPath) {
// 读取assets中的渠道配置文件(如channel_config.json)
String channelCode = readChannelFromAssets(context, configPath);
// 保存渠道码到全局变量,用于后续数据上报
SdkConfig.getInstance().setChannelCode(channelCode);
}
而渠道配置文件可能就类似以下内容:
{
"appid": 10001,
"channel": "10003"
}
管理后台点击渠道分包实际上就是触发 Python 的自动化流程实现 解压→替换→重打包.
注意: 本地生成包解压修改内容之后需要通过
apksigner(AndroidSDK自带)或者apktool(第三方)重新打包并签名
另外还有一种方式就是就是配置文件只保存特殊密钥, 在 SDK初始化 的时候发起网络请求;
如果本地配置不存在就请求 https://version/api/channel/[特殊密钥] 拉取配置加载缓存到本地之中, 后续就是加载读取这个配置文件.
这种网络请求方式仁者见仁智者见智, 依赖网络(首次启动需联网拉取配置)且对于单机应用来说可能会让玩家困惑(单机游戏也用到网络)
不过这种网络拉取配置更灵活, 可以放更多配置(渠道ID|支付参数|是否开启广告等远程控制设置), 所以这方面具体还是看需求决定
这部分后台管理通过脚本(如Python|Shell)或工具链就是执行以下步骤, 一般推荐还是在服务端打包处理:
| 步骤 | 操作细节 | 核心工具/逻辑 |
|---|---|---|
| 1. 解压APK | 将基础APK解压到临时目录(如 /tmp/apk_temp/),获取内部文件结构(包括 assets/channel_config.json 路径) |
系统 unzip 命令或 apktool d(apktool解压更保留安卓特有的资源结构) |
| 2. 替换配置文件 | 删除临时目录中原有 assets/channel_config.json,将后台生成的渠道专属配置文件写入对应路径 |
系统文件操作API(如Python的 shutil、Java的 File 类) |
| 3. 重新打包为未签名APK | 将修改后的临时目录重新压缩为“未签名APK”(如 unsigned_fgame.apk),注意压缩格式需符合安卓规范(如不压缩 .so、.dex 文件) |
apktool b(apktool打包会自动处理安卓特有的文件格式)或 zip 命令(需手动排除无需压缩的文件) |
| 4. 重新签名 | 使用发行方预先存储在后台的签名证书(如.jks文件,密码通过加密存储),对未签名APK进行签名,生成最终可安装的渠道包 | Android SDK的 apksigner(推荐,支持V2签名,兼容性更好)或 jarsigner(旧版V1签名) |
| 5. 校验与输出 | 对签名后的APK进行完整性校验(如检查是否能正常安装、渠道码是否正确读取),校验通过后存储到后台文件服务器,供运营人员下载/分发 | 调用 adb install 模拟安装(或自研校验工具),并读取APK内的 channel_config.json 验证渠道码 |
CP出包规则
CP 一般会出 aar 和 apk 两种类型包, 这两种类型对应需要发行方做不同处理:
aar: 主导方为发行方, 需要深度控制渠道包的应用配置(如权限|入口|资源)apk: 主导方为CP方, 已完成完整的游戏应用开发(含安卓工程配置), 仅需发行方 “注入渠道信息”- 若发行方需深度控制应用配置 → 选
AAR: 发行方搭工程、集成 AAR 生成 APK 母包 - 若 CP 已完成完整应用开发 → 选
APK: 发行方直接修改渠道配置和重签名