在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标签:

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



加入本地代码

我们已经完成了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 http通信——HttpURLConntection
  2. Android(安卓)解析CSV文件,中文乱码
  3. 【Android】向sdcard中写入文件
  4. Android(安卓)4.0 HttpURLConnection 下载失败问题
  5. Android(安卓)下载网络url文件并显示进度
  6. android调用浏览器打开网页链接
  7. (Android)调用百度地图api之添加覆盖物
  8. android scrollview嵌套listview出现高度显示不全解决方案
  9. NPM 和webpack 的基础使用

随机推荐

  1. android 服务
  2. Android之Touch事件分发机制
  3. Android下拉列表控件spinner-andoid学习
  4. Android Game
  5. android开机自启动的后台Service的实现 .
  6. Android获取应用程序的信息
  7. android添加开机声音
  8. Android中的设计模式--建造者模式
  9. Android 打开系统蓝牙设置
  10. android项目源码异步加载远程图片的小例