Android AAR SDK 构建

官方文档(需魔法工具): android-library

AAR(Android Archive)Android 专属的库归档格式, 可以理解为和后段的 Jar 包类似的格式:

特性 AAR(Android专属) Jar(通用Java)
包含内容 编译后的class文件 + 资源(布局/图片/字符串) + AndroidManifest.xml + 原生库(so) 仅编译后的class文件 + 清单(可选)
适用场景 Android组件封装(自定义View、Activity、带资源的工具库) 纯Java逻辑封装(无Android资源依赖)
依赖Android框架 强依赖(需Android SDK编译) 无依赖(可跨平台)
资源处理 自带资源打包,主工程可直接引用库内资源 无资源打包能力

本篇以 海外游戏上架发行方 来说明 AAR 在其中起到什么作用, 其他相关扩展知识可以去网上获取

在现代化当中开发之中, 手机游戏上架涉及到以下方面:

  • 游戏研发方: 负责开发游戏和生成 Android 应用

  • 游戏发行方: 负责提供自己公司的 SDK 和相关参数给研发接入

游戏发行方两者的资源是拆分开的, 也就是 研发方不可能获得底层发行方SDK代码, 发行方不可能获取研发的游戏开发资源.

在双方都不能获取对方源码之下, 需要发行方生成 aar 包让研发方引入并且调用内部关键 SDK 的接口功能, 从而唤醒发行方所有需要对应的接口功能.

在开发 aar 包之前, 首先必须说明的是发行 SDK 除非官方已经强制要求升级, 否则尽量采用 Java1.8 支持.

目前国内游戏 Android 端普遍版本不会更新太快(有的还在用 Unity2007 版本), 内置的打包机制可能没办法做高版本兼容;
所以开发通用的 aar 包尽量基于 Java 1.8, 甚至最好连 Kotlin 都不要采用而是继续保持 Java 包适配.

最好 Gradle 打包脚本还是用 Groovy DSL 默认, 不要用 Kotlin DSL 做打包脚本, 很多网站资料都不好找

还有 Gradle + AGP 的适配也是一团乱码, AGP(Android Gradle Plugin) 是将 Android 项目(Apk/AAR)Gradle 核心编译工具

AGP 两者必须要强关联版本对应, 因为 Gradle 的每次都带有破坏性更新, 所以不像 Java 后端大部分情况下可能无痛升级.

这里提供常用的 Gradle + AGP 版本对应:

  • Gradle 7.0 → AGP 7.0.0+

  • Gradle 7.1 → AGP 7.1.0+

  • Gradle 7.2 → AGP 7.2.0+

  • Gradle 7.3 → AGP 7.3.0+

  • Gradle 7.4 → AGP 7.4.0+

  • Gradle 7.5 → AGP 7.4.2+

注意: Gradle 7.x 以上版本推荐构建环境为 JDK11, 而编译目标为 JDK1.8

项目构建

首先最好用 IDEA/AndroidStudio 打开个 空目录 以此为开发目录, 执行以下操作:

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

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

  3. 这里采用 Android API 28 即可(目前适用面最广), 最后点击 Create 创建项目

Android Library

我这里其实默认 Module Namepino-lib, 图片上面没展示出来

创建完成就会在当前目录下面生成了 pino-lib, 这就是我们自己定义的 sdk 库.

修改配置

接下来就是创建项目根打包配置:

1
2
# 注意: 这里目录其实就是之前所说的 `空目录`, 后续我们都是以此路径为根目录 
touch build.gradle # 手动创建编译说明文件

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 来做版本锁定, 否则依赖全部乱飞:

  1. 找到项目根目录下 gradle/wrapper/gradle-wrapper.properties(没有就创建)

  2. 文件的 distributionUrl 设置为国内 gradle-wrapper 地址

  3. 设置地址 distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v7.5.0/gradle-7.5-all.zip

  4. 地址上面的版本(v7.5.0/7.5)按照自身需要调整, 代码版本提交需要将 gradle-wrapper.properties 一起提交上去锁定

最后的 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

这样就能明显加速 gradle-wrapper 下载速度, 之后就是配置第三方镜像库和引入 AGP.

现在就是在之前的 pino-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
plugins {
// 声明 Android Library
id 'com.android.library'
}

android {
namespace 'io.meteorcat.pino'
//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 ToolsAndroid SDK Platform Tools, 继续 Tools 的版本(目前工具最新是 36)

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

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

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

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

  8. Gradle JVM 选择和打包一样的 JDK, 目前是 Java17

之后刷新最新配置然后等待下载编译完成, 通过之后就是准备把自己创建 pino-lib 引入到全局.

编译出包

现在就需要测试下打出需要 AAR 包, 这里修改 pino-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
plugins {
// 声明 Android Library
id 'com.android.library'
}

android {
namespace 'io.meteorcat.pino'
//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')


// AndroidX核心库
implementation "androidx.core:core:1.17.0"
implementation "androidx.appcompat:appcompat:1.7.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.3.0'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

之后在根目录创建以下文件:

  • settings.gradle: 全局项目配置

  • local.properties: 编写本地的设置, 比如本地 AndroidSDK 地址(不要提交版本库)

  • gradle.properties: 编译全局属性, 这里需要追加 AndroidX 的支持

settings.gradle 内容如下:

1
2
3
4
5
// 自定义根目录的项目名称
rootProject.name = "PinoGame"

// 引入自定义的 Pino 库目录
include(":pino-lib")

local.properties 内容如下:

1
2
// 这里是我本地的 AndroidSDK 路径
sdk.dir=/data/Android/Sdk

gradle.properties 内容如下:

1
2
# Android 目前版本都需要开启 AndroidX
android.useAndroidX=true

重新刷新 Gradle 之后就可以看到自定义的库项目, 这样就可以准备具体的业务代码开发, 最后编译出包如下图所示:

Android Package

有些其他配置和第三方的包可以额外引入, 比如 okhttp 这种网络库等, 不过基本框架就是这样处理.

测试引入

现在编写自定义的 Android Application 用于被游戏研发商引用实现:

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
package io.meteorcat.pino;

import android.app.Activity;
import android.util.Log;

/**
* 用于测试编写自定义构建的 Activity 层
*
*/
public class PinoActivity extends Activity {

/**
* Activity 启动回调
*/
@Override
protected void onStart() {
super.onStart();
Log.i("PinoSDKActivity", "onStart");
}

/**
* Activity 重启回调
*/
@Override
protected void onRestart() {
super.onRestart();
Log.i("PinoSDKActivity", "onStart");
}

/**
* Activity 恢复回调
*/
@Override
protected void onResume() {
super.onResume();
Log.i("PinoSDKActivity", "onResume");
}

/**
* Activity 停止回调
*/
@Override
protected void onStop() {
super.onStop();
Log.i("PinoSDKActivity", "onStop");
}

/**
* Activity 销毁回调
*/
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("PinoSDKActivity", "onDestroy");
}
}

重写编译出包, 将输出的 pino-lib-debug.aar 包复制出来备用.

一般这个包在 pino-lib/build/outputs/aar 目录下

后面创建 app 模块来测试引用实现是否成功, 和创建 pino-lib 一样 New Moudle, 不过信息方面不一样:

Android App

这里选择创建 Empty Activity, 之后创建 pino-app/libs 目录将之前复制的 pino-lib-debug.aar 放置进去.

最后就是在 pino-app/build.gradle 编译文件引入下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dependencies {

// 外部扩展库, 需要创建 libs 目录放置 jar 和 aar 文件
// 该配置会默认将内部的 '*.jar' 和 '*.aar' 自动引入, 无需手动来声明引入处理
api fileTree(include: ['*.jar', '*.aar'], dir: "${projectDir}/libs")


// 版本有问题 Android 和 AndroidX 互相冲突
// 同时需要注意: AndroidX 相关组件版本最好和内部定义版本号一致
//implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1' // 1.6.1支持compileSdk 33

testImplementation 'junit:junit:4.13.2'

// 因为 libs 的包开启了 AndroidX 所以会和下面互相冲突
//androidTestImplementation 'com.android.support.test:runner:1.0.2'
//androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

androidTestImplementation 'androidx.test:runner:1.5.0' // 适配33
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' // 适配33
}

最后就是设置 appActivity, 继承我们的 pino-lib 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package io.meteorcat.pino.app;

import android.os.Bundle;
import io.meteorcat.pino.PinoActivity; // 这就是 lib 内部的 Activity

/**
* 继承实现自定义上级 Activity
*/
public class MainActivity extends PinoActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}

最后设置我们的 MainActivity 来打包启动, 打开 pino-app/src/main/AndroidManifest.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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>

<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PinoGame">
<!-- android:name 就是内部 Activity 类关键字 -->
<!-- android:exported 对于 Android 12+ 版本需显式声明 exported -->
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<!-- 这部分是默认 Activity 的核心配置 -->
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>


</manifest>

执行 Android 编译打包之后启动, 查看 Logcat 检索 PinoSDKActivityTag 就能查看到回调到自定义 SDK 的内容:

Android Logcat

网络库依赖

大部分库都是依赖于网络, 一般常用的都是简单 JSON 交换数据, 最多加上需要 MD5 哈希数据的需求, 所以只需要依赖以下组件:

  • OkHttp: 基础网络库

  • gson: JSON 处理库

  • commons-codec: MD5 处理库

所以在对应封装的 pino-lib 发行 aar 追加依赖:

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
android {
// 其他略

// 打包生成类型
buildTypes {

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

// 更新: 追加 DEBUG 全局变量传入内部, 测试环境开启调试日志
buildConfigField "boolean", "DEBUG", "true"
}


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

// 更新: 追加 DEBUG 全局变量传入内部, 生产环境关闭调试日志
buildConfigField "boolean", "DEBUG", "false"
}
}
}

dependencies {
// 其他略

// OkHttp 核心(网络请求)
implementation 'com.squareup.okhttp3:okhttp:5.3.2'
implementation 'com.squareup.okhttp3:logging-interceptor:5.3.2' // 日志拦截


// Apache Commons Codec(MD5哈希)
implementation 'commons-codec:commons-codec:1.20.0'

// Gson(JSON解析,轻量易用)
implementation 'com.google.code.gson:gson:2.13.2'

}

那么这里就可以创建 MD5Utils 工具类来专门做哈希 MD5 处理:

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
package io.meteorcat.pino.utils;

import org.apache.commons.codec.digest.DigestUtils;

import java.nio.charset.StandardCharsets;

/**
* MD5工具类
*/
public class MD5Utils {

/**
* 字符串MD5加密(默认UTF-8编码)
*
* @param content 待加密字符串
* @return 32位小写MD5结果
*/
public static String encrypt(String content) {
return DigestUtils.md5Hex(content.getBytes(StandardCharsets.UTF_8));
}

/**
* 字节数组MD5加密
*
* @param bytes 待加密字节数组
* @return 32位小写MD5结果
*/
public static String encrypt(byte[] bytes) {
return DigestUtils.md5Hex(bytes);
}

/**
* 带盐值的MD5加密(增强安全性)
*
* @param content 待加密字符串
* @param salt 盐值(建议服务端约定固定值/动态值)
* @return 32位小写MD5结果
*/
public static String encryptWithSalt(String content, String salt) {
return encrypt(content + salt);
}
}

同时封装简单的 Web 请求库 SimpleHttpUtils:

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package io.meteorcat.pino.utils;

import android.util.Log;
import com.google.gson.Gson;
import io.meteorcat.pino.BuildConfig;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* 简易 HTTP 请求工具(优化版)
*/
public class SimpleHttpUtils {

/**
* JSON 请求的 MEDIA 类型
*/
public static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");

/**
* Gson 实例, 静态统一
*/
private static final Gson GSON = new Gson();

/**
* OkHttp客户端(单例)
*/
private static final OkHttpClient okHttpClient;

/**
* 默认日志标签
*/
private static final String TAG = "SimpleHttpUtils";

static {
// 添加日志拦截器(仅调试模式),方便排查问题
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> Log.d(TAG, "OkHttp: " + message));
loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);

// 构建 HTTP 客户端
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor) // 调试日志
.retryOnConnectionFailure(true) // 弱网自动重试
.build();
}

/**
* POST请求:JSON参数 + MD5签名
*
* @param url 接口地址
* @param params 请求参数(Map)
* @param salt MD5盐值
* @param clazz 解析目标类
* @return 解析后的实体类,失败返回null
*/
public static <T> T fetch(String url, Map<String, Object> params, String salt, Class<T> clazz) {
// 空值校验
if (url == null || url.isEmpty() || clazz == null) {
Log.e(TAG, "fetch: 参数异常(url/clazz不能为空)");
return null;
}
try {
String paramStr = buildParamStr(params);
String sign = MD5Utils.encryptWithSalt(paramStr, salt);
Map<String, Object> finalParams = params == null ? new HashMap<>() : new HashMap<>(params);
finalParams.put("sign", sign);

String jsonParams = GSON.toJson(finalParams);
RequestBody requestBody = RequestBody.create(jsonParams, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();

Response response = okHttpClient.newCall(request).execute();
// 完整状态码处理
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body.contentLength() > 0) {
String responseBody = body.string();
Log.d(TAG, "fetch success: " + responseBody);
return GSON.fromJson(responseBody, clazz);
} else {
Log.e(TAG, "fetch: 响应体为空");
}
} else {
Log.e(TAG, "fetch: 接口返回非200状态码 " + response.code());
}
} catch (Exception e) {
Log.e(TAG, "fetch error", e);
}
return null;
}

/**
* GET请求:URL参数 + MD5签名
*
* @param url 接口地址
* @param params 请求参数(Map)
* @param salt MD5盐值
* @param clazz 解析目标类
* @return 解析后的实体类,失败返回null
*/
public static <T> T query(String url, Map<String, Object> params, String salt, Class<T> clazz) {
if (url == null || url.isEmpty() || clazz == null) {
Log.e(TAG, "query: 参数异常(url/clazz不能为空)");
return null;
}
try {
String paramStr = buildParamStr(params);
String sign = MD5Utils.encryptWithSalt(paramStr, salt);
String urlWithParams = buildUrlWithParams(url, params, sign);

Request request = new Request.Builder()
.url(urlWithParams)
.get()
.build();

Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body.contentLength() > 0) {
String responseBody = body.string();
Log.d(TAG, "query success: " + responseBody);
return GSON.fromJson(responseBody, clazz);
} else {
Log.e(TAG, "query: 响应体为空");
}
} else {
Log.e(TAG, "query: 接口返回非200状态码 " + response.code());
}
} catch (Exception e) {
Log.e(TAG, "query error", e);
}
return null;
}

/**
* 按Key排序拼接参数(避免签名不一致)
* 示例:{a:1, b:2} → "a=1&b=2"
*/
private static String buildParamStr(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return "";
}
List<Map.Entry<String, Object>> sortedEntries = new ArrayList<>(params.entrySet());
sortedEntries.sort(Map.Entry.comparingByKey());

StringBuilder sb = new StringBuilder();
for (int i = 0; i < sortedEntries.size(); i++) {
Map.Entry<String, Object> entry = sortedEntries.get(i);
// 过滤null值,避免签名错误
if (entry.getValue() != null) {
sb.append(entry.getKey()).append("=").append(entry.getValue());
if (i != sortedEntries.size() - 1) {
sb.append("&");
}
}
}
return sb.toString();
}

/**
* 拼接URL和参数(含签名)
*/
private static String buildUrlWithParams(String url, Map<String, Object> params, String sign) {
return (url.contains("?") ? url : url + "?") +
buildParamStr(params) +
"&sign=" + sign;
}
}

编译生成之后将 aar 包重新放入 app 项目, 注意如果需要用到网络权限需要在 AndroidManifest.xml 追加配置:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 其他略 -->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

之后就能够测试调用网络封装的网络请求方法了, 至此就可以准备编写内部所需的 SDK 功能业务.