Android 反编译注入

如果接触过 游戏联运 和 广告变现 基本上都对 渠道分包 有一定了解, 主要核心就是应用反编译

本质上就是需要给游戏应用提供或者上传一份应用证书签名, 然后方便后台界面触发解包反编译注入对应参数之后再打包成新的渠道包

这里有以下概念需要了解

  • 母包: 开发商输出无渠道参数的原始未签名/测试签名游戏安装包, 也就是等待被解包并重新打包

  • 签名文件

    • Android: 渠道方提供正式签名 keystore(.jks/.keystore)/别名/密钥密码; 部分自研渠道使用自有统一证书
    • iOS: 渠道开发者证书/描述文件/推送证书
    • 之所以需要证书, 是因为应用上架/渠道校验/支付唤起/广告 SDK 校验都强制校验签名, 母包分包后必须用新的渠道证书重新签名
  • 反编译/解包: 利用 apktool 或者解压方式修改内部核心配置文件, 将所需参数注入进去

这部分其实更适合有经验客户端来讲解, 但是有时候部分服务端可以简单处理下自定义集成 SDK 库, 从而实现类似效果

环境搭建

推荐编辑器为 IDEA 或者 Android Studio

之后就是构建自己的打包 SDK 类库, 这部分我将其命名为 nova-lib, 需要按以下步骤

  1. 创建空目录, 并且用 IDEA/Android Studio 打开

  2. 依次点击 File > New > New Module

  3. 在随即显示的 Create New Module 信息框中填写, 依次点击 Android Library 并填写相关库信息

  4. 这里采用 Android API 28 即可(目前兼容性最广), 最后点击 Create 创建项目

  5. Build Configuration 这项是打包脚本, 官方虽然主推 kotlin 脚本打包方式(但是我不太推荐)

具体的配置参数如下所示

android-1

之后需要修改配置, 首先是项目根目录下创建 build.gradle 文件, 并且添加以下配置内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 全局脚本打包配置
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()
}
dependencies {
// 注意: 使用与Gradle 7.x 兼容的AGP版本
classpath "com.android.tools.build:gradle:7.4.2"
}
}

// 设置所有项目的默认配置第三方源
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()
}
}

// 新版本命令注册写法, 注意好兼容性(有的版本没有各种方式)
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

之后就是采用 GradleWrapper 来做版本锁定, 否则依赖全部乱飞

找到项目根目录下 gradle/wrapper/gradle-wrapper.properties(没有就创建) 将内容替换成以下方式

1
2
3
4
5
6
7
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v7.5.0/gradle-7.5-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

在编辑器当中点击 Build > Build Project 确认输出编译成功即可, 之后就是配置第三方镜像库和引入 AGP

在 build.gradle(不存在就创建) 添加以下打包配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plugins {
// 声明 Android Library
id 'com.android.library'
}

android {
namespace 'me.meteorcat.nova'
//noinspection GradleDependency
compileSdk 33 // 目前 AGP-7.4.2 版本最高只能支持到 33

defaultConfig {
minSdk 28

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {

}

如果 build.gradle 编译打包文件会出现以下错误

  • Your build is currently configured to use incompatible Java 21.0.9 and Gradle 7.5

这是因为目前的没有指定 JDK 来编译打包, 默认选择系统 SDK, 这里最好下载 Android SDK 来打包

  1. 依次点击 File > Settings... > Languages & Frameworks > Android SDK Updater

  2. 在 SDK Platform 勾选之前创建的 Android API SDK 版本(之前选择 Android API 28, 对应的是 Android 9)

  3. 在 SDK Tools 勾选 Android Build Tools 和 Android SDK Platform Tools, 继续 Tools 的版本(目前工具最新是 36)

  4. 选中之后点击 OK 就会弹出提示安装 Android 打包编译工具

  5. 之后就是选择编译的 JDK, 依次点击 File > Project Structure > Project

  6. 选中当前打包的 JDK, 我这边系统采用 Java11

  7. 依次点击 File > Settings... > Build,Execution,Deployment > Build Tools > Gradle

  8. 在 Gradle JVM 选择和打包一样的 JDK(一般采用默认 Project SDK 即可)

如果 JDK 版本不符会出现异常错误 Your build is currently configured to use incompatible {Jdk版本} and {Gradle版本}

重新构建下项目一般不会出现其他问题, 之后就是需要细化修改 nova-lib/build.gradle 的配置, 推荐以下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
plugins {
// 声明 Android Library
id 'com.android.library'
}

android {
namespace 'me.meteorcat.nova'
//noinspection GradleDependency
compileSdk 33 // 目前 AGP-7.4.2 版本最高只能支持到 33

defaultConfig {
minSdk 28

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

// 编译选项配置 - 目标为JDK 1.8
compileOptions {
// 源代码兼容性
sourceCompatibility JavaVersion.VERSION_1_8
// 目标代码兼容性
targetCompatibility JavaVersion.VERSION_1_8
// 启用desugar支持Java 8+特性在旧设备上运行
coreLibraryDesugaringEnabled true
}

// 排除文件
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 {

debug {
// 调试开关:允许 Debugger 附加(必须为 true,否则无法调试)
debuggable true
// 代码混淆:关闭(混淆会破坏调试信息,开发阶段无需混淆)
minifyEnabled false
// 自定义 Debug 版本的版本名后缀(如 1.0.0-debug)
versionNameSuffix "-debug"
}


release {
// 调试开关:关闭(防止正式包被调试,保障安全性)
debuggable false
// 混淆规则文件:默认的 Android 优化规则 + 自定义的项目规则
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 自定义 Release 版本的版本名后缀(如 1.0.0-release)
versionNameSuffix "-release"
}
}
}

dependencies {

// 外部扩展库, 需要创建 extra 目录放置 jar 和 aar 文件
api fileTree(include: ['*.jar', '*.aar'], dir: 'extra')

// 统一Kotlin版本, 全局只用1.8.10
// 老版本 AGP 会因为 kotlin 出现异常
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10"

// AndroidX核心库
implementation "androidx.core:core:1.10.1"
implementation "androidx.appcompat:appcompat:1.6.1"

// 支持Java 8+特性的desugar库
//noinspection GradleDependency
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'

// 测试库
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

这里会发现打包文件都是黑色的不可用的, 需要在项目根目录建立 settings.gradle 文件, 添加子目录模块

1
2
// 注册子模块
include ':nova-lib'

如果编译的时候出现以下错误内容

  • Task 'prepareKotlinBuildScriptModel' not found in project ':nova-lib'.

    • 默认编辑器启用 KotlinDSL 支持而找不到脚本所以错误, 关闭窗口删除 .idea 和 build 目录重新打开自动加载
  • SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable

    • 这是因为 Gradle 找不到安卓 SDK 路径, 可能是没有配置 ANDROID_HOME 环境变量, 按照以下方法处理
    • 配置 ANDROID_HOME 环境变量指向 Android/Sdk 路径
    • 在根目录创建 local.properties 文件, 内部设置 sdk.dir=D\\Android\\Sdk SDK目录(路径分隔符用双反斜杠\\)
    • 本地开发我一般习惯配置 ANDROID_HOME 环境变量, 具体按照个人需求来选择处理
  • Set android.useAndroidX=true in the gradle.properties file and retry.

    • 目前Google强制需要添加 AndroidX 相关支持
    • 需要在项目根目录的 gradle.properties(没有该文件就创建) 文件添加如下配置
    • android.useAndroidX=true: 开启AndroidX
    • android.enableJetifier=true: 关闭旧支持库自动迁移警告

后续重新编译项目确认没问题, 接下来就是自定义可被注入的配置文件

定义渠道配置

一般母包注入以下以下参数

  • app_ident: 后台系统的母包唯一标识, 指向数据库当中 app_base.app_ident 应用

  • app_key: 后台系统分配给接入商的客户端密钥, 用于做请求参数签名处理

  • channel_ident: 分包的渠道唯一标识, 指向数据库渠道表 app_channel.channel_ident 应用

目前有两种分包处理方式, 这部分可以了解下

  • AndroidManifest: 直接修改 application 节点内容附加对应渠道参数

  • assets: 在 assets 目录之中创建独有的配置文件, 接入该集成库的客户端会读取到文件配置

一般建议 assets 创建独有配置文件即可, AndroidManifest 主要问题是怕配置名可能会出现冲突

这里创建独有的配置文件 nova_config.json, 文件放入 nova-lib/src/main/assets/nova_config.json 中, 内容如下

1
2
3
4
5
{
"app_ident": "",
"app_key": "",
"channel_ident": ""
}

这里 app_ident/app_key 留空是提供给应用接入方去手动填写, 反编译分包的时候会重新覆盖写入

现在就需要在 nova-lib 之中编写功能类, 暴露给接入方来调用和加载数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package me.meteorcat.nova;

import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
* NovaSDK渠道注入配置读取工具
*
* @see <a href="https://doc.meteorcat.me">Document</a>
*/
public class NovaConfig {

/**
* 静态配置文件名
*/
private static final String CONFIG_FILE = "nova_config.json";

/**
* 单例句柄
*/
private static NovaConfig instance;

/**
* 不允许实例化
*/
private NovaConfig() {
}

/**
* 获取静态单例
*
*/
public static NovaConfig getInstance() {
if (instance == null) {
instance = new NovaConfig();
}
return instance;
}


/**
* 对应 nova_config.json 的 app_ident 参数
*/
private String appIdentifier;

/**
* 获取应用标识
*
*/
public String getAppIdentifier() {
return appIdentifier;
}


/**
* 对应 nova_config.json 的 app_key 参数
*/
private String appKey;

/**
* 获取客户端参数签名KEY
*/
public String getAppKey() {
return appKey;
}


/**
* 对应 nova_config.json 的 channel_ident 参数
*/
private String channelIdentifier;


/**
* 渠道渠道标识
*/
public String getChannelIdentifier() {
return channelIdentifier;
}


/**
* 初始化加载方法, 用于调用加载读取配置文件
* 抛出的异常由业务层进行兜底
*/
public void init(Context ctx) throws IOException, JSONException {

// 加载配置文件
InputStream is = ctx.getAssets().open(CONFIG_FILE);
byte[] buffer = new byte[is.available()];
int ignore = is.read(buffer);
is.close();

// 读取转化JSON文件
String json = new String(buffer, StandardCharsets.UTF_8);
JSONObject data = new JSONObject(json);

// 加载到配置类之中
this.appIdentifier = data.optString("app_ident", "");
this.appKey = data.optString("app_key", "");
this.channelIdentifier = data.optString("channel_ident", "");
}
}

配置加载类一般比较简单, 这里仅仅只是做母包注入处理方法, 执行打包之后就会生成对应 arr 库

假设我们现在已经要开始做某个渠道了, 这里打包出我们最新合作的 arr 第三方库:

android-2

这里打包出 nova-lib-debug.aar(测试包) 和 nova-lib-release.aar(正式包), 一般推荐只需要给 release.aar 即可

接下来就是接入商来引入该 aar 对接到自己的安卓应用当中

创建应用

这里首先在根目录创建新的模块, 也就是模拟我们应用接入的步骤来实现接入

  1. 依次点击 File > New > New Module

  2. 在随即显示的 Create New Module 信息框中填写, 依次点击 Phone & Tablet 并填写应用信息

  3. 需要注意下一步, 如果采用 Java 语言就不需要选择 Empty Activity, 而是要选择 Empty Views Activity

  4. 最后设置 Activity 名称, 这里直接保持默认交 MainActivity/activity_main 即可

配置基本信息

选择 Activity

配置 Activity

这里没有配置调试虚拟机, 所以需要创建下测试虚拟机来测试运行我们创建的 NovaGame 应用

这里建议选择我们之前的 Android API 28 版本, 部分游戏厂商SDK是偏老的

安卓模拟器

这里直接运行可能会出现报错, 因为默认生成的项目工程内部的 androidx.activity 版本比较新(android特有的破坏性更新)

为了保持兼容 compileSdk 33 就必须要降级指定的 androidx.activity, 在 game 目录的 build.gradle 修改以下包

1
2
3
4
5
6
7
8
// 追加强制版本约束, 强制约束依赖版本, 避免传递依赖拉取1.8.0
configurations.all {
resolutionStrategy.force "androidx.activity:activity:1.7.2"
}

dependencies {
// 引入的包信息
}

这里处理编译之后会报相关 Android resource linking failed 的错误, 实际上就是对应的图标文件丢失

  1. 打开 game/src/main/AndroidManifest.xml 文件

  2. 找到 <application> 标签内容

  3. 将 android:icon 和 android:roundIcon 相关修改存在图标或者注释掉

之后可能会报相关的 EdgeToEdge 错误, 该组件是 androidx.activity 1.8.0 初始化自带(没什么用), 可以直接删除引入的相关类

完成之后就可以看到具体 Hello.World 界面

启动应用

现在就是准备把 aar 包引入并且在窗口当中打印相关参数

引入SDK

这里直接在应用目录创建 libs 目录, 也就是创建 game/libs 目录, 目录结构如下

1
2
3
4
5
6
7
8
9
10
game/
├── src/
│ └── main/
│ ├── java/
│ ├── res/
│ └── assets/
├── libs/
│ └── nova-lib-release.aar
├── build.gradle
└── AndroidManifest.xml

nova-lib-release.aar 就是之前我们建议生成的渠道SDK库, 但是目前需要给应用追加应用初始化类方便给 nova-lib 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package me.meteorcat.game;

import android.app.Application;
import android.util.Log;
import me.meteorcat.nova.NovaConfig;

import java.util.Objects;

/**
* 注册应用初始化
*/
public class IApplication extends Application {

/**
* 应用标识
*/
private static final String TAG = "NovaGame";

/**
* 应用启动回调
*/
@Override
public void onCreate() {
super.onCreate();
// 核心初始化:读取assets渠道配置
try {
NovaConfig.getInstance().init(this);
} catch (Exception e) {
Log.e(TAG, Objects.requireNonNull(e.getMessage()));
throw new RuntimeException(e);
}

// 日志打印调试
NovaConfig cfg = NovaConfig.getInstance();
Log.d(TAG, "app_ident=" + cfg.getAppIdentifier());
Log.d(TAG, "app_key=" + cfg.getAppKey());
Log.d(TAG, "channel_ident=" + cfg.getChannelIdentifier());
}

}

编写完成之后在 AndroidManifest.xml 注册该初始化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- android:name 就是核心注册类名 -->
<application
android:name=".IApplication"
android:allowBackup="true"
android:icon="@android:drawable/sym_def_app_icon"
android:roundIcon="@android:drawable/sym_def_app_icon"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.GameHost">

<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

之后重新编译启动应用查看打印日志就可以看到具体内容信息

输出配置

在游戏项目的目录创建新的 nova_config.json 文件, 确认是否可以加载指定配置文件的参数

配置文件地址 game/src/main/assets/nova_config.json, 文件内容如下

1
2
3
4
5
{
"app_ident": "s34x123x",
"app_key": "7d2f9c4e8b1a3056zxcvbnmqwertyu",
"channel_ident": ""
}

再次启动应用之后确认日志打印是否一致

日志显示

这里为了方便看到后续渠道分包效果, 建议在页面上将配置文件打印出来, 所以需要在 MainActivity.java 文件修改处理下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package me.meteorcat.game;

import android.os.Bundle;

import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import me.meteorcat.nova.NovaConfig;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 布局加载
setContentView(R.layout.activity_main);

// 渠道SDK初始化
NovaConfig novaConfig = NovaConfig.getInstance();

// 拼接展示文本
String infoBuilder = "===== 渠道分包配置信息 =====\n" +
"母包唯一标识 app_ident:" + novaConfig.getAppIdentifier() + "\n\n" +
"客户端密钥 app_key:" + novaConfig.getAppKey() + "\n\n" +
"渠道唯一标识 channel_ident:" + novaConfig.getChannelIdentifier() + "\n\n";

// 将参数渲染到页面文本框
TextView tvChannelInfo = findViewById(R.id.nova_info);
tvChannelInfo.setText(infoBuilder);


ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

这里需要在 res/layout/activity_main.xml 其中添加文本节点用于展示信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<!--原来的不需要展示 -->
<!-- <TextView-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Hello World!"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"/>-->

<TextView
android:id="@+id/nova_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:lineSpacingExtra="8sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

/>

</androidx.constraintlayout.widget.ConstraintLayout>

再次启动也能够在页面上面看到具体内容信息

展示信息

确认没问题后接下来就是出包流程, 出包的话就需要关键的应用包签名证书, 这就是后面需要说明的

出包流程

应用接入方(也就是对接的研发方)一般出包的情况如下

  • 已经在对应的平台获取到签名证书(比如平台会生成专属应用签名证书)

  • 平台要求上传包必须用该证书签名才允许上架

那么作为SDK提供方, 我们一般会要求对方编译生成原始母包, 也就是 {应用包名}-release-unsigned.apk

安卓生成母包比较简单, 在 IDEA 之中直接点击出包即可

母包生成

如图片所见这里 game-release-unsigned.apk 就是生成母包, 也就是上传到对应管理后台来等待渠道分包

这里提供该 apk 方便自己了解测试: game-release-unsigned.apk

现在就是核心的分包脚本命令, 后续会采用 Python 做全自动打包, 目前为了加深理解所以会以命令方式处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 注意: 这里采用 linux 的 bash 环境命令
mkdir -p channel/xiaomi # 创建小米渠道目录, 编译打包的产物在这里处理
cd channel/xiaomi # 进入小米渠道打包目录

# 这里一般是用脚本把母包复制过来, 这部分需要调取数据的 app_base 母包应用表文件路径
# 我为了简约直接手动复制, 后续做自动化打包需要执行处理包的下载处理流程

# Linux 命令, 获取文件的大小, 单位: byte
stat -c "%s" game-release-unsigned.apk

# 这里需要生成替换的注入的配置文件 nova_config.json
# 这个文件也是需要从数据库当中取出渠道信息并且以 JSON 格式保存的文件
# 这里我手动生成该文件, 内容如下
# {"app_ident":"s34x123x","app_key":"7d2f9c4e8b1a3056zxcvbnmqwertyu","channel_ident":"xiaomi"}
# 在原基础追加 channel_ident 信息
stat -c "%s" nova_config.json # 查看配置文件大小, 只是看看是否存在文件

# 没问题就准备反编译解包
# 这里需要用到 apktool 工具: https://apktool.org/
# 具体看官网配置即可, 按照下面命令输入打印成功就代表安装成功
# 这里不采用全局环境安装, 而是在上级目录配置打包环境内容
java -jar ../apktool.jar v # 打包工具放置在上一级通用


# --------------------------
# 开始核心步骤解包注入
# --------------------------

# 1. 解包原始母包, 输出至临时文件夹 unpack
java -jar ../apktool.jar d -f game-release-unsigned.apk -o unpack

# 2. 防止如果解包后无assets文件夹, 需要先创建再复制
mkdir -p unpack/assets

# 3. 覆盖assets目录下的渠道配置
cp nova_config.json unpack/assets/nova_config.json

# 4. 重新打包资源并生成未签名中间包
java -jar ../apktool.jar b unpack -o unsigned.apk



# --------------------------
# 签名证书使用
# --------------------------

# 这里的证书一般是数据库提取出来本地文件或者CDN文件下载
# 一般不会在打包的时候才去生成证书, 而是在管理后台为对应渠道生成渠道签名证书

# 首先生成 keystore JKS 证书, 直接配置静默生成
# -storepass 和 -keypass 就是核心证书密码, 这部分其实应该数据库获取, 这里直接演示 123456 即可
../keytool.exe -genkey -v \
-keystore xiaomi.jks \
-alias xiaomi_key \
-keyalg RSA \
-keysize 2048 \
-validity 36500 \
-storepass 123456 \
-keypass 123456 \
-dname "CN=NovaGame, OU=Dev, O=Company, L=Guangzhou, ST=Guangdong, C=CN"


# 之后替换证书路径/库密码/证书别名
# 目前大部分平台都要求 APK 上架商店强制要求4K对齐, 这一步最好加上
# 一般使用 apksigner 签名之后默认4K对齐, 该工具在 `Android/Sdk/build-tools/{API版本}/lib/apksigner.jar` 之中
# --ks-pass 和 --key-pass 就是之前配置的 JKS 证书密码
java -jar ../apksigner.jar sign \
--ks xiaomi.jks \
--ks-key-alias xiaomi_key \
--ks-pass pass:123456 \
--key-pass pass:123456 \
--min-sdk-version 28 \
--rotation-min-sdk-version 28 \
--v1-signing-enabled true \
--v2-signing-enabled true \
--v3-signing-enabled true \
--out xiaomi_align.apk \
unsigned.apk


# 签名合法性验证
java -jar ../apksigner.jar verify --verbose xiaomi_align.apk


# 至此完成渠道分包功能, 查看最终渠道成品APK字节大小
# xiaomi_align.apk 就是小米指定渠道包
stat -c "%s" xiaomi_align.apk

# 至此完成整体的渠道分包流程

最终就可以看到分包注入的参数

成功获取

简单注入渠道信息的包: xiaomi_align.apk

这就是渠道简单分包原理, 这部分其实需要结合管理后台业务来看, 具体证书这部分其实应该有管理后台控制

不过涉及到的知识点就比较广, 并且一般是有客户端编写 Python 跨平台脚本来运行