Cocos2d-x Android平台配置(JNI调用/权限申请)
【摘要】 引言在Cocos2d-x跨平台开发中,Android平台因其碎片化特性和严格的权限模型,成为最复杂的部署目标之一。开发者不仅需要处理不同CPU架构(armeabi-v7a、arm64-v8a、x86)的兼容性问题,还必须熟练掌握JNI(Java Native Interface)调用机制与Android 6.0+动态权限申请流程。本文将基于Cocos2d-x 3.8+,系统讲解Android...
引言
技术背景
1. Android与Cocos2d-x的交互架构
C++游戏逻辑 ↔ JNI桥接层 ↔ Java原生代码 ↔ Android系统API
-
JNI作用:实现C++与Java的双向调用,是Cocos2d-x调用Android原生功能(如振动、传感器、支付)的唯一途径。 -
权限模型:Android 6.0(API 23)引入动态权限,危险权限(如相机、存储)需在运行时向用户申请。 -
构建系统:Cocos2d-x使用CMake管理Android NDK编译,Gradle负责打包APK。
2. 关键组件
-
NDK:Native Development Kit,提供C/C++编译工具链。 -
CMake:跨平台构建工具,替代ndk-build。 -
AndroidManifest.xml:声明权限、Activity、服务。 -
gradle.properties:配置编译选项(如minSdkVersion)。
应用场景
|
|
|
|
|---|---|---|
|
|
|
android.os.Vibrator |
|
|
|
WRITE_EXTERNAL_STORAGE权限 |
|
|
|
TelephonyManager(Android 10+受限) |
|
|
|
|
|
|
|
|
核心代码实现
1. 环境准备与配置文件
1.1 AndroidManifest.xml(权限声明)
<!-- proj.android/app/AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.cocos2dx.cpp">
<!-- 基础权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" /> <!-- Android 10+作用域存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 硬件加速 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<!-- Cocos2d-x Activity -->
<activity
android:name="org.cocos2dx.cpp.AppActivity"
android:label="@string/app_name"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
1.2 CMakeLists.txt(NDK编译配置)
# proj.android/app/CMakeLists.txt
cmake_minimum_required(VERSION 3.18.1)
project("MyGame")
# 设置Cocos2d-x路径
set(COCOS2DX_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../cocos2d)
include_directories(${COCOS2DX_ROOT} ${COCOS2DX_ROOT}/cocos ${COCOS2DX_ROOT}/external)
# 添加C++源文件
file(GLOB_RECURSE GAME_SRC
${CMAKE_CURRENT_SOURCE_DIR}/../../Classes/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../Classes/platform/android/*.cpp)
add_library(game SHARED ${GAME_SRC})
# 链接Cocos2d-x库
target_link_libraries(game
cocos2d
cocos_network
android
log)
# NDK版本配置
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
1.3 gradle.properties(编译选项)
# proj.android/gradle.properties
org.gradle.jvmargs=-Xmx4096m
android.enableJetifier=true
android.useAndroidX=true
# 最低支持的SDK版本
android.minSdkVersion=21
# 目标SDK版本
android.targetSdkVersion=33
# 编译SDK版本
android.compileSdkVersion=33
2. JNI调用实现(C++ ↔ Java)
2.1 Java原生工具类(功能封装)
// proj.android/app/src/org/cocos2dx/cpp/AndroidUtils.java
package org.cocos2dx.cpp;
import android.app.Activity;
import android.content.Context;
import android.os.Vibrator;
import android.os.Build;
import android.provider.Settings;
import android.Manifest;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
public class AndroidUtils {
private static Activity sActivity;
private static Vibrator sVibrator;
private static final int REQUEST_CODE_PERMISSIONS = 1001;
// 初始化(由AppActivity调用)
public static void init(Activity activity) {
sActivity = activity;
sVibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
}
// 振动(毫秒)
public static void vibrate(long milliseconds) {
if (sVibrator != null && sVibrator.hasVibrator()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
sVibrator.vibrate(android.os.VibrationEffect.createOneShot(milliseconds,
android.os.VibrationEffect.DEFAULT_AMPLITUDE));
} else {
sVibrator.vibrate(milliseconds);
}
}
}
// 检查并申请权限
public static boolean checkAndRequestPermissions(String[] permissions) {
if (sActivity == null) return false;
ArrayList<String> neededPermissions = new ArrayList<>();
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(sActivity, perm) !=
android.content.pm.PackageManager.PERMISSION_GRANTED) {
neededPermissions.add(perm);
}
}
if (!neededPermissions.isEmpty()) {
ActivityCompat.requestPermissions(sActivity,
neededPermissions.toArray(new String[0]), REQUEST_CODE_PERMISSIONS);
return false;
}
return true;
}
// 获取设备型号
public static String getDeviceModel() {
return Build.MODEL;
}
// 获取Android版本
public static String getOSVersion() {
return Build.VERSION.RELEASE;
}
// 权限回调(供C++层监听)
public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != android.content.pm.PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
nativeOnPermissionsResult(allGranted);
}
}
// Native回调声明
private static native void nativeOnPermissionsResult(boolean granted);
}
2.2 C++ JNI桥接层
// Classes/platform/android/AndroidBridge.h
#ifndef __ANDROID_BRIDGE_H__
#define __ANDROID_BRIDGE_H__
#include "cocos2d.h"
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
USING_NS_CC;
class AndroidBridge {
public:
static void vibrate(int milliseconds);
static bool checkPermissions(const std::vector<std::string>& permissions);
static std::string getDeviceModel();
static std::string getOSVersion();
// 静态初始化(绑定JNI方法)
static void initJNI();
private:
static void onPermissionsResult(JNIEnv* env, jobject obj, jboolean granted);
};
#endif // __ANDROID_BRIDGE_H__
// Classes/platform/android/AndroidBridge.cpp
#include "AndroidBridge.h"
// JNI方法注册
static const char* CLASS_NAME = "org/cocos2dx/cpp/AndroidUtils";
void AndroidBridge::initJNI() {
JniMethodInfo methodInfo;
// 绑定nativeOnPermissionsResult
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"nativeOnPermissionsResult", "(Z)V")) {
// 方法签名:(Z)V 表示参数为boolean,返回void
}
}
void AndroidBridge::vibrate(int milliseconds) {
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "vibrate", "(J)V")) {
jlong ms = milliseconds;
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, ms);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
}
bool AndroidBridge::checkPermissions(const std::vector<std::string>& permissions) {
JniMethodInfo methodInfo;
if (!JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"checkAndRequestPermissions", "([Ljava/lang/String;)Z")) {
return false;
}
// 转换C++ vector为Java String[]
jobjectArray javaArray = methodInfo.env->NewObjectArray(
permissions.size(),
methodInfo.env->FindClass("java/lang/String"),
nullptr);
for (size_t i = 0; i < permissions.size(); ++i) {
jstring str = methodInfo.env->NewStringUTF(permissions[i].c_str());
methodInfo.env->SetObjectArrayElement(javaArray, i, str);
methodInfo.env->DeleteLocalRef(str);
}
jboolean result = methodInfo.env->CallStaticBooleanMethod(
methodInfo.classID, methodInfo.methodID, javaArray);
methodInfo.env->DeleteLocalRef(javaArray);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
return result;
}
std::string AndroidBridge::getDeviceModel() {
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"getDeviceModel", "()Ljava/lang/String;")) {
jstring model = (jstring)methodInfo.env->CallStaticObjectMethod(
methodInfo.classID, methodInfo.methodID);
std::string result = JniHelper::jstring2string(model);
methodInfo.env->DeleteLocalRef(model);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
return result;
}
return "";
}
std::string AndroidBridge::getOSVersion() {
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"getOSVersion", "()Ljava/lang/String;")) {
jstring version = (jstring)methodInfo.env->CallStaticObjectMethod(
methodInfo.classID, methodInfo.methodID);
std::string result = JniHelper::jstring2string(version);
methodInfo.env->DeleteLocalRef(version);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
return result;
}
return "";
}
// JNI回调实现(供Java调用)
extern "C" {
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AndroidUtils_nativeOnPermissionsResult(
JNIEnv* env, jclass clazz, jboolean granted) {
// 转发到C++层(可通过回调函数或事件分发)
CCLOG("Permissions result: %s", granted ? "GRANTED" : "DENIED");
}
}
2.3 AppActivity(Cocos2d-x入口)
// proj.android/app/src/org/cocos2dx/cpp/AppActivity.java
package org.cocos2dx.cpp;
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class AppActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化Cocos2d-x
org.cocos2dx.lib.Cocos2dxActivity.onCreate(this, savedInstanceState);
// 初始化Android工具类
AndroidUtils.init(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 转发权限结果到工具类
AndroidUtils.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 处理第三方SDK回调(如支付)
}
}
3. 游戏逻辑调用示例
// Classes/HelloWorldScene.cpp
#include "AndroidBridge.h"
bool HelloWorld::init() {
if (!Layer::init()) return false;
// 获取设备信息
std::string model = AndroidBridge::getDeviceModel();
std::string osVer = AndroidBridge::getOSVersion();
CCLOG("Device: %s, OS: %s", model.c_str(), osVer.c_str());
// 按钮:触发振动
auto btnVibrate = ui::Button::create("button.png", "button_pressed.png");
btnVibrate->setTitleText("振动");
btnVibrate->setPosition(Vec2(200, 200));
btnVibrate->addClickEventListener([](Ref* sender) {
// 先检查振动权限(Android 13+需要)
std::vector<std::string> perms = {"android.permission.VIBRATE"};
if (AndroidBridge::checkPermissions(perms)) {
AndroidBridge::vibrate(500); // 振动500ms
} else {
CCLOG("请授予振动权限");
}
});
this->addChild(btnVibrate);
// 按钮:申请存储权限
auto btnStorage = ui::Button::create("button.png", "button_pressed.png");
btnStorage->setTitleText("申请存储权限");
btnStorage->setPosition(Vec2(400, 200));
btnStorage->addClickEventListener([](Ref* sender) {
std::vector<std::string> perms = {
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"
};
AndroidBridge::checkPermissions(perms);
});
this->addChild(btnStorage);
return true;
}
原理解释
1. JNI调用流程
-
C++ → Java:通过 JniHelper::getStaticMethodInfo查找Java方法,转换为jobject/jstring等类型调用。 -
Java → C++:Java类声明 native方法,C++实现JNIEXPORT函数,通过RegisterNatives注册(示例中直接使用javah生成的函数名)。 -
数据类型转换:C++字符串↔ jstring,数组↔jobjectArray,布尔值↔jboolean。
2. 权限申请流程
-
声明权限:在 AndroidManifest.xml中静态声明所需权限。 -
运行时检查:通过 ContextCompat.checkSelfPermission检查权限是否已授予。 -
动态申请:调用 ActivityCompat.requestPermissions弹出系统授权对话框。 -
结果处理:重写 onRequestPermissionsResult接收用户选择,通过JNI通知C++层。
核心特性
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
原理流程图
C++游戏逻辑 → AndroidBridge → JniHelper → 查找Java方法 → 调用Java工具类
Java工具类 → 权限检查 → 系统授权对话框 → 用户选择 → onRequestPermissionsResult → JNI回调C++
环境准备
-
Cocos2d-x 3.8+:下载官方发布版或GitHub源码。 -
Android Studio 4.2+:安装NDK(r21+)、CMake、LLDB。 -
环境变量:配置 ANDROID_HOME、NDK_ROOT。 -
测试设备:Android 7.0+真机(推荐Android 10+测试作用域存储)。
运行结果
-
应用启动后,日志输出设备型号与Android版本。 -
点击“振动”按钮,设备以500ms周期振动(需授予权限)。 -
点击“申请存储权限”,弹出系统授权对话框,选择“允许”后权限状态更新。
测试步骤
-
编译验证:在Android Studio中执行 ./gradlew assembleDebug,生成APK并安装。 -
权限测试:首次启动拒绝存储权限,观察C++层是否正确收到拒绝回调。 -
功能测试:授予权限后,测试文件读写、振动是否正常。 -
多架构测试:在arm64-v8a设备上验证性能,x86模拟器验证兼容性。
部署场景
-
Google Play:上传APK/AAB,配置签名与混淆规则。 -
国内应用商店:适配各商店SDK(如华为、小米),修改 build.gradle添加渠道号。 -
企业内部分发:使用 adb install或第三方分发平台(如蒲公英)。
疑难解答
-
JNI找不到类/方法:检查类名包路径是否与 CLASS_NAME一致,方法签名是否正确(可用javap -s查看)。 -
权限被拒绝:Android 10+需适配作用域存储,避免使用 Environment.getExternalStorageDirectory()。 -
NDK编译错误:清理 proj.android/app/build目录,重新生成CMake缓存。 -
崩溃无日志:在 AndroidManifest.xml中添加android:debuggable="true",查看Logcat。
未来展望
-
Jetpack Compose集成:Cocos2d-x与Compose混合UI开发。 -
Android App Bundle:支持Play Feature Delivery按需加载原生库。 -
Kotlin协程:Java层使用协程简化异步权限申请与网络请求。
技术趋势与挑战
-
趋势:Android权限模型持续收紧(如Android 14限制后台麦克风访问),需动态调整策略。 -
挑战:碎片化设备兼容(如折叠屏、高刷新率),需适配多窗口与SurfaceFlinger变化。
总结
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)