在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()
@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的模拟器上使用, 获取功多信息请阅读,
Android Emulator 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 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的某个地方
<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类,加入下列内容:
public class GameLibJNIWrapper {
|
这个类将加载libgame.so库,这个我们在调用本地库是会用到。创建匹配这个类的C文件,方法是建立工程,打开命令行提示,目录改到bin/class文件夹,运行下面的命令行:
javah -o ../../jni/jni.c com.learnopengles.airhockey.GameLibJNIWrapper
javah必须定位在你的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方法。这里是工作原理:
编译本地代码
编译和执行本地代码,我们需要为ndk创建系统提供描述文件。为了达到这个目的,我们需要两个文件Android.mk 和 Application.mk。当我们增加本地支持到项目中时,项目中自动增加了一个叫game.cpp的文件,这个文件不需要,所以你可以删除他。
为Android.mk做以下设置:
LOCAL_PATH := $(call my-dir)
|
|
这个文件描述了我们的代码文件,告诉ndk game.c和jni.c应该编译,然后被创建成一个共享lib库叫做libgame.so。这个库将字运行时动态链接 libGLESv2库。
编写此文件时,不要留下任何多余的空格,否则会造成创建失败。
下一个文件, Application.mk,内容如下:
|
这行语句告诉ndk项目创建使用的android版本为api 10,所以这里会在你使用了早些版本不支持的特性时发出警告,也告诉编译系统生成
ARMv7-A格式的库,这可以支持浮点数和许多android设备的新功能。
更新RendererWrapper
我们必须更新RendererWrapper以调用我们做的native代码,才能看到我们多绘制的东西,如下:
@Override
现在渲染类调用我们之前做的
GameLibJNIWrapper
类,调用jni.c的本地方法,再调用game.c。
|
运行应用
现在可以运行工程了。在创建后会有个libgame.so文件在 /libs/armeabi-v7a/中创建。运行后,程序看起来是这个样子的:
屏幕呈现红蓝交替变换。
下期预告
项目的完整代码在这里 GitHub project。更详细的介绍OpenGL ES 2,请参考 Android Lesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide。
在接下来的章节里,我们将创建一个ios项目。到时你回发现用oc重新使用common文件夹里的代码是件多么容易的事。如果有问题请留言!(作者)