您的位置:首页 > 移动开发 > Android开发

在Android中使用NDK调用OpenGl

2014-02-22 13:38 330 查看

在Android中使用NDK调用OpenGl

Calling OpenGL from C on Android, Using the NDK原文地址:http://www.learnopengles.com/calling-opengl-from-android-using-the-ndk/对于我的系列文章Developinga 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()
@Override
protected 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的模拟器上使用, 获取功多信息请阅读,AndroidEmulator Now Supports Native OpenGL ES2.0!添加一下代码完成对Activity的修改:
@Override
protected void onPause() {
super.onPause();

if (rendererSet) {
glSurfaceView.onPause();
}
}

@Override
protected void onResume() {
super.onResume();

if (rendererSet) {
glSurfaceView.onResume();
}
}
我们需要处理Android的生命周期,所以在需要的时候暂停继续游戏。只有在执行了glSurfaceView.setRenderer()后处理才回起作用,否则调用这些方法回使程序跳出。获得更多的信息,点击Android Lesson One: Getting Started or OpenGLES 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的某个地方
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
从OGl 2.0开始只支持Android2.3.3 api10以上的系统,所以替换use-sdk标签:
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="17" />
工作正常,将看到一下画面

加入本地代码

我们已经完成了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文件,加入以下内容:
这是android的OpenGl头文件。创建Jni结合,我门首先需要创建暴露native接口的Java类。创建一个calledGameLibJNIWrapper类,加入下列内容:
这个类将加载libgame.so库,这个我们在调用本地库是会用到。创建匹配这个类的C文件,方法是建立工程,打开命令行提示,目录改到bin/class文件夹,运行下面的命令行:javah -o ../../jni/jni.c com.learnopengles.airhockey.GameLibJNIWrapperjavah必须定位在你的JDKs目录。这段命令将创建一个看起来非常凌乱的jni.c文件,有很多我们不需要的东西。让我们用下面的内容替换他以简化程序:
#include "../../common/game.h"
#include <jni.h>

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做以下设置:
这个文件描述了我们的代码文件,告诉ndk game.c和jni.c应该编译,然后被创建成一个共享lib库叫做libgame.so。这个库将字运行时动态链接 libGLESv2库。编写此文件时,不要留下任何多余的空格,否则会造成创建失败。下一个文件, Application.mk,内容如下:

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

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

更新RendererWrapper

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

运行应用

现在可以运行工程了。在创建后会有个libgame.so文件在 /libs/armeabi-v7a/中创建。运行后,程序看起来是这个样子的:Second pass屏幕呈现红蓝交替变换。

下期预告

项目的完整代码在这里 GitHub project。更详细的介绍OpenGL ES 2,请参考 AndroidLesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide。在接下来的章节里,我们将创建一个ios项目。到时你回发现用oc重新使用common文件夹里的代码是件多么容易的事。如果有问题请留言!(作者)

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ios ndk jni opengl