Cocos2d-x Android平台配置(JNI调用/权限申请)

举报
William 发表于 2025/12/26 11:02:47 2025/12/26
【摘要】 引言在Cocos2d-x跨平台开发中,Android平台因其碎片化特性和严格的权限模型,成为最复杂的部署目标之一。开发者不仅需要处理不同CPU架构(armeabi-v7a、arm64-v8a、x86)的兼容性问题,还必须熟练掌握JNI(Java Native Interface)调用机制与Android 6.0+动态权限申请流程。本文将基于Cocos2d-x 3.8+,系统讲解Android...


引言

在Cocos2d-x跨平台开发中,Android平台因其碎片化特性和严格的权限模型,成为最复杂的部署目标之一。开发者不仅需要处理不同CPU架构(armeabi-v7a、arm64-v8a、x86)的兼容性问题,还必须熟练掌握JNI(Java Native Interface)调用机制Android 6.0+动态权限申请流程
本文将基于Cocos2d-x 3.8+,系统讲解Android平台的完整配置流程,涵盖环境搭建、JNI桥接、权限申请、原生功能调用等核心环节,并提供可直接集成的完整代码示例,帮助开发者快速解决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振动
C++触发设备振动
JNI调用android.os.Vibrator
文件读写(外部存储)
读写SD卡文件
动态申请WRITE_EXTERNAL_STORAGE权限
获取设备IMEI
设备唯一标识(需权限)
JNI调用TelephonyManager(Android 10+受限)
集成第三方SDK(如支付)
调用Java SDK接口
JNI封装SDK初始化与回调
后台服务与通知
游戏后台运行与推送
JNI启动Android Service,发送Notification

核心代码实现

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调用流程

  1. C++ → Java:通过JniHelper::getStaticMethodInfo查找Java方法,转换为jobject/jstring等类型调用。
  2. Java → C++:Java类声明native方法,C++实现JNIEXPORT函数,通过RegisterNatives注册(示例中直接使用javah生成的函数名)。
  3. 数据类型转换:C++字符串↔jstring,数组↔jobjectArray,布尔值↔jboolean

2. 权限申请流程

  1. 声明权限:在AndroidManifest.xml中静态声明所需权限。
  2. 运行时检查:通过ContextCompat.checkSelfPermission检查权限是否已授予。
  3. 动态申请:调用ActivityCompat.requestPermissions弹出系统授权对话框。
  4. 结果处理:重写onRequestPermissionsResult接收用户选择,通过JNI通知C++层。

核心特性

特性
说明
完整JNI桥接
支持C++调用Java所有公开方法,支持回调
动态权限适配
自动适配Android 6.0+权限模型,避免崩溃
多架构支持
自动编译armeabi-v7a/arm64-v8a/x86库
错误处理
JNI方法查找失败日志输出,避免空指针崩溃
跨版本兼容
适配Android 5.0~13的API差异(如振动API)

原理流程图

C++游戏逻辑 → AndroidBridge → JniHelper → 查找Java方法 → 调用Java工具类
Java工具类 → 权限检查 → 系统授权对话框 → 用户选择 → onRequestPermissionsResult → JNI回调C++

环境准备

  • Cocos2d-x 3.8+:下载官方发布版或GitHub源码。
  • Android Studio 4.2+:安装NDK(r21+)、CMake、LLDB。
  • 环境变量:配置ANDROID_HOMENDK_ROOT
  • 测试设备:Android 7.0+真机(推荐Android 10+测试作用域存储)。

运行结果

  • 应用启动后,日志输出设备型号与Android版本。
  • 点击“振动”按钮,设备以500ms周期振动(需授予权限)。
  • 点击“申请存储权限”,弹出系统授权对话框,选择“允许”后权限状态更新。

测试步骤

  1. 编译验证:在Android Studio中执行./gradlew assembleDebug,生成APK并安装。
  2. 权限测试:首次启动拒绝存储权限,观察C++层是否正确收到拒绝回调。
  3. 功能测试:授予权限后,测试文件读写、振动是否正常。
  4. 多架构测试:在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变化。

总结

本文详细讲解了Cocos2d-x在Android平台的完整配置流程,涵盖环境搭建、JNI调用、权限申请、原生功能集成,提供了可直接运行的代码示例。通过抽象桥接层隔离平台差异,开发者可高效实现C++与Android原生功能的交互,为复杂游戏功能(如支付、社交分享)奠定坚实基础。掌握这些技术后,可轻松应对Android平台的各类适配挑战,实现高质量的多平台游戏发布。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。