Cocos2d-x Android Studio 配置踩坑实录

背景

Cocos2d-x 虽然现在慢慢被 Creator 取代了,但还是有很多老项目需要维护。把 Cocos2d-x 项目配置到 Android Studio 里开发是个挺折腾的事,涉及 Gradle、NDK、Android.mk 多个环节。这篇文章记录我折腾过程中遇到的各种问题和解决方案。

环境准备

版本要求

组件 推荐版本 说明
Android Studio 3.0+ 支持 Gradle 4.x
Gradle 4.1 通过 wrapper 配置
Android SDK 24+ compileSdkVersion
NDK r16+ 支持 gnustl_static
Cocos2d-x 3.14+ 较稳定的版本

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cocos2d-x-project/
├── cocos2d/ # 引擎目录
│ ├── cocos/
│ ├── external/
│ └── cocos/platform/android/libcocos2dx/
├── Classes/ # 游戏代码
├── Resources/ # 资源文件
└── proj.android-studio/ # Android Studio 项目
├── app/
│ ├── build.gradle
│ ├── jni/Android.mk
│ ├── jni/Application.mk
│ └── src/
├── build.gradle
├── gradle.properties
└── settings.gradle

完整配置详解

1. 项目级 build.gradle

文件路径: proj.android-studio/build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
google()
}
}

关键配置说明:

  • gradle:2.3.3 - 与 Gradle 4.1 兼容的插件版本
  • google() - 必须添加,用于下载 Android Support 库

2. Gradle 配置

文件路径: proj.android-studio/gradle.properties

1
2
PROP_TARGET_SDK_VERSION=14
PROP_APP_ABI=armeabi-v7a

属性说明:

属性 说明 可选值
PROP_TARGET_SDK_VERSION 目标平台版本 14-28
PROP_APP_ABI 编译的 CPU 架构 armeabi-v7a, arm64-v8a, x86, x86_64

注意: armeabi 已在 NDK r17 中移除,请使用 armeabi-v7a

3. Gradle Wrapper 配置

文件路径: proj.android-studio/gradle/wrapper/gradle-wrapper.properties

1
2
3
4
5
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

版本对应关系:

Gradle 插件版本 需要的 Gradle 版本
2.3.3 3.3+
3.0.0 4.1+
3.1.0 4.4+

4. App 模块 build.gradle

文件路径: proj.android-studio/app/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
96
97
98
import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
compileSdkVersion 24
buildToolsVersion "26.0.2"

defaultConfig {
applicationId "com.game.xxx"
minSdkVersion 10
targetSdkVersion 23
versionCode 1
versionName "1.0"

externalNativeBuild {
ndkBuild {
if (!project.hasProperty("PROP_NDK_MODE") || PROP_NDK_MODE.compareTo('none') != 0) {
// skip the NDK Build step if PROP_NDK_MODE is none
targets 'cocos2dcpp'
arguments 'NDK_TOOLCHAIN_VERSION=4.9'
arguments 'APP_PLATFORM=android-'+PROP_TARGET_SDK_VERSION

def module_paths = [project.file("../../cocos2d").absolutePath,
project.file("../../cocos2d/cocos").absolutePath,
project.file("../../cocos2d/external").absolutePath]
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Windows 路径分隔符转换
module_paths = module_paths.collect {it.replaceAll('\\\\', '/')}
arguments 'NDK_MODULE_PATH=' + module_paths.join(";")
}
else {
arguments 'NDK_MODULE_PATH=' + module_paths.join(':')
}

arguments '-j' + Runtime.runtime.availableProcessors()
abiFilters.addAll(PROP_APP_ABI.split(':').collect{it as String})
}
}
}
}

sourceSets.main {
java.srcDir "src"
res.srcDir "res"
jniLibs.srcDir "libs"
manifest.srcFile "AndroidManifest.xml"
assets.srcDir "../../Resources"
}

externalNativeBuild {
ndkBuild {
if (!project.hasProperty("PROP_NDK_MODE") || PROP_NDK_MODE.compareTo('none') != 0) {
path "jni/Android.mk"
}
}
}

signingConfigs {
release {
if (project.hasProperty("RELEASE_STORE_FILE")) {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
if (project.hasProperty("RELEASE_STORE_FILE")) {
signingConfig signingConfigs.release
}

externalNativeBuild {
ndkBuild {
arguments 'NDK_DEBUG=0'
}
}
}

debug {
externalNativeBuild {
ndkBuild {
arguments 'NDK_DEBUG=1'
}
}
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':libcocos2dx')
}

配置要点:

  1. 跨平台路径处理
1
2
3
4
5
6
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
module_paths = module_paths.collect {it.replaceAll('\\\\', '/')}
arguments 'NDK_MODULE_PATH=' + module_paths.join(";")
} else {
arguments 'NDK_MODULE_PATH=' + module_paths.join(':')
}
  1. 动态指定编译线程数
1
arguments '-j' + Runtime.runtime.availableProcessors()
  1. 多 ABI 支持
1
abiFilters.addAll(PROP_APP_ABI.split(':').collect{it as String})

5. Android.mk 配置

文件路径: proj.android-studio/app/jni/Android.mk

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
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# 添加 Cocos2d-x 模块搜索路径
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/external)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos/audio/include)

LOCAL_MODULE := cocos2dcpp_shared
LOCAL_MODULE_FILENAME := libcocos2dcpp

# 递归遍历获取所有源文件
define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

# 去重函数
define uniq
$(eval seen :=) $(foreach _,$(1),$(if $(filter ${_},${seen}),,$(eval seen += ${_}))) ${seen}
endef

ALLFILES = $(call walk, $(LOCAL_PATH)/../../../Classes)

FILE_LIST := $(filter %.cpp, $(ALLFILES))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

# 添加额外的 C 文件
LOCAL_SRC_FILES += ../../../Classes/Libs/SQLite/sqlite3secure.c

# 获取所有 include 目录
FILES_PATH := $(LOCAL_PATH)/../../../Classes
FILE_INCLUDES := $(dir $(foreach src_path,$(FILES_PATH), $(call walk,$(src_path),*/) ) )
FILE_INCLUDES := $(call uniq,$(FILE_INCLUDES))

LOCAL_C_INCLUDES := $(FILE_INCLUDES)

# 链接 Cocos2d-x 静态库
LOCAL_STATIC_LIBRARIES := cocos2dx_static

include $(BUILD_SHARED_LIBRARY)

# 导入 Cocos2d-x 模块
$(call import-module,.)

关键技巧:

  1. 自动递归收集源文件

    • walk 函数递归遍历 Classes 目录
    • 自动发现所有 .cpp 文件,无需手动添加
  2. 自动收集头文件目录

    • uniq 函数去重处理
    • 自动包含所有子目录作为 include 路径

6. Application.mk 配置

文件路径: proj.android-studio/app/jni/Application.mk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
APP_STL := gnustl_static

APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char
APP_LDFLAGS := -latomic

APP_SHORT_COMMANDS := true
APP_CPPFLAGS += -Wno-error=format-security

ifeq ($(NDK_DEBUG),1)
APP_CPPFLAGS += -DCOCOS2D_DEBUG=1
APP_OPTIM := debug
else
APP_CPPFLAGS += -DNDEBUG
APP_OPTIM := release
endif

配置说明:

配置项 说明
APP_STL 使用 GNU STL 静态库
-frtti 启用运行时类型信息
-std=c++11 启用 C++11 支持
APP_SHORT_COMMANDS 解决 Windows 命令行长度过长问题
COCOS2D_DEBUG Debug 模式宏定义

7. libcocos2dx 库配置

文件路径: cocos2d/cocos/platform/android/libcocos2dx/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
apply plugin: 'com.android.library'

android {
compileSdkVersion 24
buildToolsVersion "26.0.2"

defaultConfig {
minSdkVersion 10
targetSdkVersion 24
versionCode 1
versionName "1.0"
}

sourceSets.main {
aidl.srcDir "../java/src"
java.srcDir "../java/src"
manifest.srcFile "AndroidManifest.xml"
}

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

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}

常见错误与解决方案

错误 1:format-security 错误

错误信息:

1
error: format not a string literal and no format arguments [-Werror=format-security]

解决方案:

方法 1:修改 NDK 配置(不推荐)

1
2
# 修改 ndk/build/core/default-build-commands.mk
TARGET_FORMAT_STRING_CFLAGS := -Wformat #-Werror=format-security

方法 2:在 Application.mk 中添加(推荐)

1
APP_CPPFLAGS += -Wno-error=format-security

方法 3:修改源代码(彻底解决)

1
2
3
4
5
// 修改前
env->GetStringUTFChars(name_, 0);

// 修改后
env->GetStringUTFChars(name_, NULL);

错误 2:CreateProcess 参数错误

错误信息:

1
error: process_begin: CreateProcess(...) make (e=87): 参数错误

原因: Windows 对命令行参数数量有限制,Android.mk 中包含的源文件太多导致超限。

解决方案:

1
2
# 在 Application.mk 中添加
APP_SHORT_COMMANDS := true

这会生成中间文件,避免命令行过长。

错误 3:malloc 未声明

错误信息:

1
'malloc' was not declared in this scope

解决方案:

1
#include <stdlib.h>

错误 4:void* 转换错误

错误信息:

1
invalid conversion from 'void*' to 'char*'

解决方案:

1
2
// 添加显式类型转换
char* buffer = (char*)malloc(size);

错误 5:ABI 不支持

错误信息:

1
2
3
4
Android NDK: INTERNAL ERROR: The armeabi ABI should have exactly one architecture definitions.

Error: ABIs [armeabi] are not supported for platform.
Supported ABIs are [armeabi-v7a, arm64-v8a, x86, x86_64].

解决方案:

1
2
3
4
5
6
# 修改 gradle.properties
# 旧配置
PROP_APP_ABI=armeabi

# 新配置
PROP_APP_ABI=armeabi-v7a

错误 6:Gradle 版本不兼容

错误信息:

1
Error: Unsupported method: BaseConfig.getApplicationIdSuffix().

解决方案:

1
2
3
4
5
6
// 修改 build.gradle 中的 Gradle 插件版本
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}

编译与调试

命令行编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 进入项目目录
cd proj.android-studio

# 清理项目
./gradlew clean

# 编译 Debug 版本
./gradlew assembleDebug

# 编译 Release 版本
./gradlew assembleRelease

# 安装到设备
./gradlew installDebug

调试配置

在 Android Studio 中配置 C++ 调试:

  1. Edit Configurations → 添加 Android Native
  2. Module: app
  3. Debugger: Dual (Java + Native)

日志过滤

1
2
3
4
5
// 在 C++ 代码中使用
#include "cocos2d.h"

CCLOG("Debug message: %s", message);
CCLOGERROR("Error: %d", errorCode);

ADB 日志过滤:

1
adb logcat -s cocos2d-x:D *:S

性能优化

编译优化

1
2
3
# Application.mk
APP_CPPFLAGS += -O3 -DNDEBUG
APP_OPTIM := release

减小 APK 体积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// build.gradle
android {
defaultConfig {
// 只编译必要的 ABI
ndk {
abiFilters 'armeabi-v7a'
}
}

buildTypes {
release {
// 启用代码压缩
minifyEnabled true
shrinkResources true
}
}
}

小结

Cocos2d-x 配置到 Android Studio 的几条经验:

  1. 版本匹配 - Gradle 插件与 Gradle 版本要对应
  2. 路径处理 - Windows 需要特殊处理路径分隔符
  3. ABI 选择 - 避免使用已废弃的 armeabi
  4. 命令长度 - Windows 下使用 APP_SHORT_COMMANDS
  5. 代码规范 - 修复 format-security 警告,使用显式类型转换

希望这篇文章能帮你少走弯路,顺利把 Cocos2d-x 项目跑起来。