在Android中使用NDK调用OpenGl

Calling OpenGL from C on Android, Using the NDK

原文地址:http://www.learnopengles.com/calling-opengl-from-android-using-the-ndk/

对于我的系列文章Developing a Simple Game of Air Hockey Using C++ and OpenGL ES 2 for Android, iOS, and the Web的第一节,我们需要用opengl创建一个简单的android工程,这里会用到本地代码渲染场景。

预备知识

android sdk ndk 和一个合适的ide

模拟器或一个支持opengl es 2.0的android设备

本次课程里我们将使用eclipse

测试本次教程里的代码,我使用adt 22.0.1 和 platform 17 ,ndk 8e 和 Eclipse Juno Service Pack 2。

准备工作

首先创建一个新工程,带NDK支持的那种,当然你也可以从 GitHub project获取代码。

在创建新项目之前,建立一个airhockey文件夹,然后建立一个git文件夹,Git可以帮助你管理源代码,比如在你出现错误代码时恢复,学习更多的内容,请点击Git documentation。

File->New->Android Application Project,然后取名为airHockey,application name设置为Air Hockey,包名设置为com.learnopengles.airhockey,其他的选项默认,或者自己填写,保存项目到我们刚才创建的文件夹中。

创建好后,右击project,选择Android Tools->Add Native Support,提问library名称时,输入game,则创建出的库名称为libgame.so,这将在项目文件夹中建立一个jni文件夹。

初始化OpenGl

项目创建后,现在我们可以修改activity和configurate来加载OpenGl,首先我们先在Activity类中添加两个变量

private GLSurfaceView glSurfaceView;private boolean rendererSet;
现在设置onCreate()

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);     ActivityManager activityManager        = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);    ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();     final boolean supportsEs2 =        configurationInfo.reqGlEsVersion >= 0x20000 || isProbablyEmulator();     if (supportsEs2) {        glSurfaceView = new GLSurfaceView(this);         if (isProbablyEmulator()) {            // Avoids crashes on startup with some emulator images.            glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);        }         glSurfaceView.setEGLContextClientVersion(2);        glSurfaceView.setRenderer(new RendererWrapper());        rendererSet = true;        setContentView(glSurfaceView);    } else {        // Should never be seen in production, since the manifest filters        // unsupported devices.        Toast.makeText(this, "This device does not support OpenGL ES 2.0.",                Toast.LENGTH_LONG).show();        return;    }}
首先检测设备是否支持OpenGl ES 2.0 ,支持的话创建一个GlSurfaceView。

configurationInfo.reqGlEsVersion >= 0x20000在模拟器上不可用,所以我们添加

private boolean isProbablyEmulator() {    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1            && (Build.FINGERPRINT.startsWith("generic")                    || Build.FINGERPRINT.startsWith("unknown")                    || Build.MODEL.contains("google_sdk")                    || Build.MODEL.contains("Emulator")                    || Build.MODEL.contains("Android SDK built for x86"));}
OpengGl ES 2.0只能在启用了Host GPU的模拟器上使用, 获取功多信息请阅读, Android Emulator Now Supports Native OpenGL ES2.0!
添加一下代码完成对Activity的修改:

@Overrideprotected void onPause() {    super.onPause();     if (rendererSet) {        glSurfaceView.onPause();    }} @Overrideprotected void onResume() {    super.onResume();     if (rendererSet) {        glSurfaceView.onResume();    }}
我们需要处理Android的生命周期,所以在需要的时候暂停继续游戏。只有在执行了glSurfaceView.setRenderer()后处理才回起作用,否则调用这些方法回使程序跳出。
获得更多的信息,点击 Android Lesson One: Getting Started or  OpenGL ES 2 for Android: A Quick-Start Guide。

创建一个RendererWrapper类

public class RendererWrapper implements Renderer {    @Override    public void onSurfaceCreated(GL10 gl, EGLConfig config) {        glClearColor(0.0f, 0.0f, 1.0f, 0.0f);    }     @Override    public void onSurfaceChanged(GL10 gl, int width, int height) {        // No-op    }     @Override    public void onDrawFrame(GL10 gl) {        glClear(GL_COLOR_BUFFER_BIT);    }}

这个简单的渲染程序用蓝色背景清除屏幕。稍后我们将把这些方法换成c++程序。调用这些方法不用添加gl前缀,只需添加android.opengl.GLES20.*在文件顶部,然后选择Source->Organize Imports。

如果你在编译的时候遇到了错误,确保你加入了下面所有的引用

import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;import android.opengl.GLSurfaceView.Renderer;
修改manifest排除不支持OpenGl的设备

把下面的代码加入到manifest的某个地方

从OGl 2.0开始只支持Android2.3.3 api10以上的系统,所以替换use-sdk标签:

工作正常,将看到一下画面


在Android中使用NDK调用OpenGl_第1张图片

加入本地代码

我们已经完成了java上的代码,但我们真正想做的用本地代码调用OpenGl,应该怎么做呢?下面我们将创建一个NDK项目,并把opengl代码移植到c文件里。

为了将来能让ios和web平台共享我们制作的本地代码,所以我们需要在项目文件夹上层建立一个common文件夹,这意味这,在你的Air Hockey项目中有一个android文件夹,里面存放android项目,common文件夹中存放公用代码。

用eclipse连接一个项目文件夹外的文件夹有些麻烦,分以下几步:

右击项目选择属性,Resource->Linked Resources然后选择New

输入COMMON_SRC_LOC作为名称,位置为‘${PROJECT_LOC}\..\common’然后点击Ok。

右键点击项目选择Build Path->Link Source…, 选择 Variables…, 选择 COMMON_SRC_LOC,点击Ok,输入common作为文件夹的名称选择Finish。

现在项目里出现了一个新文件夹common。

接下来我们要在common文件夹中创建两个文件game.c和game.h,方法为右键点击项目选择New File。将一下内容添加进game.h:

void on_surface_created();void on_surface_changed();void on_draw_frame();
c语言中,.h为头文件,这个文件作为.c文件的声明。这里包含三个我们将在Java中调用的函数。

在game.c中加入下面内容。

#include "game.h"#include "glwrapper.h" void on_surface_created() {    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);} void on_surface_changed() {    // No-op} void on_draw_frame() {    glClear(GL_COLOR_BUFFER_BIT);}

这些代码可以用红色清除屏幕,且每帧都会进行清除操作。我们使用一个glwrapper.h去包含OpenGl平台特征库,这个文件在其他平台的不同位置都有出现。
加入平台特征码和JNI码

使用这些代码,我们只需要两样东西:定义glwrapper.h和一些JNI结合代码。JNI可以替代Java本地接口,这是在Android上让Java和C互相调用的方法。

在工程中的jni文件夹创建一个glwrapper.h文件,加入以下内容:

#include 

这是android的OpenGl头文件。创建Jni结合,我门首先需要创建暴露native接口的Java类。创建一个calledGameLibJNIWrapper类,加入下列内容:

public class GameLibJNIWrapper {
    static {        System.loadLibrary("game");    }     public static native void on_surface_created();     public static native void on_surface_changed(int width, int height);     public static native void on_draw_frame();}

这个类将加载libgame.so库,这个我们在调用本地库是会用到。创建匹配这个类的C文件,方法是建立工程,打开命令行提示,目录改到bin/class文件夹,运行下面的命令行:

javah -o ../../jni/jni.c com.learnopengles.airhockey.GameLibJNIWrapper

javah必须定位在你的JDKs目录。这段命令将创建一个看起来非常凌乱的jni.c文件,有很多我们不需要的东西。让我们用下面的内容替换他以简化程序:

#include "../../common/game.h"#include  JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1created    (JNIEnv * env, jclass cls) {    on_surface_created();} JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1changed    (JNIEnv * env, jclass cls, jint width, jint height) {    on_surface_changed();} JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1draw_1frame    (JNIEnv * env, jclass cls) {    on_draw_frame();}

好的文件修改完了,且加入了game.h引用,这样可以调用我们的game方法。这里是工作原理:

GameLibJNIWrapper 定义了可以从Java调用的本地c方法。 为了能从Java进行调用,必须用特殊的方式进行命名,每个方法至少有两个参数,几个 JNIEnv指针,和 jclass 。为了方便,我们可以使用javah创建带前缀的jin.c。 我们从jin.c调用game.h定义的方法。这样Java调用本地代码的连接就完成了。

编译本地代码

编译和执行本地代码,我们需要为ndk创建系统提供描述文件。为了达到这个目的,我们需要两个文件Android.mk 和 Application.mk。当我们增加本地支持到项目中时,项目中自动增加了一个叫game.cpp的文件,这个文件不需要,所以你可以删除他。

为Android.mk做以下设置:

LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS) LOCAL_MODULE    := gameLOCAL_CFLAGS    := -Wall -WextraLOCAL_SRC_FILES := ../../common/game.c jni.cLOCAL_LDLIBS := -lGLESv2 include $(BUILD_SHARED_LIBRARY)

这个文件描述了我们的代码文件,告诉ndk game.c和jni.c应该编译,然后被创建成一个共享lib库叫做libgame.so。这个库将字运行时动态链接 libGLESv2库。

编写此文件时,不要留下任何多余的空格,否则会造成创建失败。

下一个文件, Application.mk,内容如下:

APP_PLATFORM := android-10APP_ABI := armeabi-v7a

这行语句告诉ndk项目创建使用的android版本为api 10,所以这里会在你使用了早些版本不支持的特性时发出警告,也告诉编译系统生成

ARMv7-A格式的库,这可以支持浮点数和许多android设备的新功能。

更新RendererWrapper

我们必须更新RendererWrapper以调用我们做的native代码,才能看到我们多绘制的东西,如下:

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {    GameLibJNIWrapper.on_surface_created();} @Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {    GameLibJNIWrapper.on_surface_changed(width, height);} @Overridepublic void onDrawFrame(GL10 gl) {    GameLibJNIWrapper.on_draw_frame();}
现在渲染类调用我们之前做的 GameLibJNIWrapper  类,调用jni.c的本地方法,再调用game.c。

运行应用

现在可以运行工程了。在创建后会有个libgame.so文件在 /libs/armeabi-v7a/中创建。运行后,程序看起来是这个样子的:

Second pass

屏幕呈现红蓝交替变换。

下期预告

项目的完整代码在这里 GitHub project。更详细的介绍OpenGL ES 2,请参考 Android Lesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide。

在接下来的章节里,我们将创建一个ios项目。到时你回发现用oc重新使用common文件夹里的代码是件多么容易的事。如果有问题请留言!(作者)



更多相关文章

  1. android 打开各种文件的方式
  2. 在Eclipse 中关联android sdk 源代码
  3. iOS、Android获取文件头信息
  4. 操作内存卡的常用代码
  5. Android通过Uri获取文件的路径的方法
  6. 【Android】使用Git控制Android程序的gitignore文件
  7. 启动 flutter项目时报Could not find com.android.tools.build:g
  8. 完成android的manven项目管理

随机推荐

  1. 初学Android,FrameLayout霓虹灯效果(五)
  2. 应用系统样式延伸
  3. 《Expert Android》关键点摘录之二
  4. 简单播放系统提示音 android
  5. Android Binder设计与实现 – 设计篇
  6. android - Shader 着色器 翻译
  7. Android 技巧 - 网络可用性侦测
  8. Android全屏设置
  9. ReactNative js判断android还是ios
  10. Android实时抓取日志,生成文件