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

GLES2 Graphic Engine Practice(二)Android平台ndk编程

2012-12-16 21:33 489 查看
Android游戏大多还是Java编写,而且2维居多。主要是由于曾今的J2SE流行、手机性能以前还没现今这么强大、屏幕大小有限、手机游戏需要简单操作等原因。我在公司里面写的Android游戏还算是C++,不过底层跨平台的引擎用的还是别家公司的(其实就用了它能把C++代码单纯移植到Android那一点)。

市面上支持C++的Android平台的游戏引擎有Marmalade,像cocos2d(最近似乎也出了cocos2d-x支持C++)以前只可以用Java开发,其他Android的Java引擎很多。最热的Unity是什么都有,一般用C#,但是也可以用C++写Plugin扩展。

总而言之,由于C++代码的执行效率表现、以及使用受众数量。C++还是很有地位滴。引擎底层用C/C++,可以提供复杂高效绘图的可能性。Google也是看到这一点,把ndk(native code dev toolkit)端给开发者,就是说虽然Android上的Java虚拟机已经能让一般的应用很流畅执行,做一个手机游戏Java就够了,但是你要写C++也可以。(Java再快还是不及C++的吧…汗)

回到正题,一些工作做好了才把能在Android上执行的程序框架完成。

1. 环境搭建

这个是比较麻烦的一个过程,来回折腾难免的,我碰到的问题也许和别人不完全一样,但基本上大家都是按这几步过来的。

1) Android SDK和ndk.

在官网上可以下载到,Android SDK是先下载更新器,然后自己拉Contents的,我就拷贝了很早以前下下来的,有兴趣自己下的同学参考Google和Baidu,别人的博文质量比这里的三言两语要好。

2) Eclipse、Cygwin,CDT、ADT。

Cygwin提供Android的Linux编译环境。Eclipse是标配IDE,CDT帮你在Eclipse里编辑C++工程,ADT帮你在Eclipse里开发Android程序,可以在Android SDK下完后在Eclipse里配置。

这篇“搭建NDK环境(Cygwin+android-NDK)”有参考follow了一下,还不错:

/article/2014765.html

另外建议初学的话还是从Android SDK sample的Java程序看起,一下子看ndk的sample的话,可能对Android的应用程序的特点会不熟,如果Java的觉得还可以,做ndk的得处理些jni代码。

有碰到.so能在sample中编译生成、离开ndk目录就不行的问题,后来就不把工程拷出sample目录就没事了。.so可以顺利生成,apk也OK了。Eclipse里编译工程Java就是只刷新就可以了,ndk的东西clean一下才会重编译(配置见上面引用),或者把工程从工作区去掉,再载入时也会重编译。

工程上面右键->Properties->Builders->New做编译器的配置时Arguments的写法如下

--login -c "cd /cygdrive/含有Android.mk的jni工程目录&&$ANDROID_NDK_ROOT/ndk-build"

比如我的一个工程是这么写的

--login -c "cd/cygdrive/E/Program__Files/android-ndk-r5c/samples/ZTgeoDemo2Mesh/jni&&$ANDROID_NDK_ROOT/ndk-build"

!注意工程路径变掉了,这里也一定要随之更新。

2. 工程编译设置

android编译系统是依靠类似makefile的编译信息文件Android.mk来执行的。

我的Android.mk如下:

LOCAL_PATH:=
$(call my-dir)

include$(CLEAR_VARS)

LOCAL_MODULE := libgl2jni

#LOCAL_CFLAGS := -Werror

LOCAL_CFLAGS :=-DFT2_BUILD_LIBRARY

LOCAL_CFLAGS +=-DFT_CONFIG_OPTION_SYSTEM_ZLIB

LOCAL_C_INCLUDES:= Common/freetype2/include

LOCAL_C_INCLUDES+= Common/zlib

LOCAL_SRC_FILES := Common/freetype2/include/ft2build.h

LOCAL_SRC_FILES +=Common/freetype2/src/autofit/autofit.c

。。。省略当中添加需编译源文件(LOCAL_SRC_FILES+=)的一百多行。

LOCAL_STATIC_LIBRARIES+= libgnustl_static

LOCAL_LDLIBS := -L$(NDK_ROOT)/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi\

-llog -lGLESv2

include$(BUILD_SHARED_LIBRARY)

LOCAL_MODULE 指定工程中所有代码编译后生成动态链接库libgl2jni.So。

LOCAL_CFLAGS为编译器定义额外的标志(如宏定义)
FT2_BUILD_LIBRARY和FT_CONFIG_OPTION_SYSTEM_ZLIB是编译freetype2源码需要的宏定义。

LOCAL_C_INCLUDES为附加的头文件目录

LOCAL_SRC_FILES 列举所以需要编译的源码文件。我把pnglib、freetype2、zlib还有引擎里自己的源码的需要编译的文件都写在了这里。

LOCAL_STATIC_LIBRARIES 需要的静态库,其中引用了gnu的stl库。

LOCAL_LDLIBS 额外的库。。

写法详细的Android.mk写法见:

/article/5953886.html

为了 使用gnu的stl,还需要在Application.mk文件中加入一些如RunTimeTypeIdentify和异常支持的设置,内容如下:

APP_STL := gnustl_static

APP_CPPFLAGS += -frtti

APP_CPPFLAGS +=-fexceptions

基本上这么设置就可以编译代码了。能生成.so就意味编译成功了。

编译时我也碰到一些问题,下面说一下如何解决的:

3. 编译error排除

UnsatisfiedLinkError: jni函数的实现无法找到会报此error,仔细检查C的函数名,参数是否和Java的声明一致。

Jni函数声明还必须按一定规则做,不然也不能编译正确。

比如包(package)名为com.ztgeo.demo,一个Java类名为GL2JNILib,包含native方法init,对应在C中带2个int参数的jni函数的签名就会如下:

JNIEXPORT voidJNICALL Java_com_ztgeo_demo_GL2JNILib_init(JNIEnv * env, jobject obj,
jint width, jint height)

即C函数声明是[Java+下划线+以下划线分割的包名+下划线+Java类名+Java函数名]

GCC(G++)和VC编译器语法有些差异,一些在VC中编译能过的G++不能编译通过:

o 比如GCC中引用全局namespace函数必须显式地用using关键字。

o GCC中,模板函数中声明模板类型对象需要使用关键字typename

o 常量字符串在VC里字符可以包含中文等非AsciII字符,GCC不可以

o GCC中类型(常量非常量)转换限制比VC严格

用了std::wstring,在GCC中竟然报错说是未知类型。后来在网上查到可以自己创建wstring:

typedef basic_string<wchar_t> wstring;

按照stl容器类型和算法分离的原则,这样子就简简单单地完成了wstring。


4. 运行时debug

在写Font的Demo时,开始是Crash,后来是能跑了,字画不出。。于是在怀疑的代码周围加了不少Log,最后发现罪魁祸首是sprintf这个函数。

在VC里正常的sprintf,GCC就不知为何出了问题了,拼接出来的字符串里所有字符值都错了。于是改用std::ostringstream来格式化字符,OK,字符都正确了。

真机上的调试光有Log,还是比较吃力,有些吃不准的地方加Log再编译运行看结果,不行的话得再加Log再重复。。

所以说.so文件的c++代码出错了,能立刻定位到出错的函数的话,会方便一些。按照/article/8087985.html

上介绍的方法,把.so反编译后确实查到了出错的函数。有一两个空指针问题就是这样查到的。

5. 资源管理和suspend & resume处理

1) 资源管理

Android应用一般会把资源放入assert目录,eclipse会生成R.java记录每个放入assert目录的资源的id,Java代码可以使用资源Id访问资源。相对ndk的情况没那么简单。有以下几个处理方式。

a. 读取apk,然后读取资源:ndk 1.6之后支持的AAssetManage可以使用nativecode访问apk。或者在Java端把apk的path拿到,再由nativecode使用zip库读取。

b. 把资源重命名或者合并为一个.so文件,放在libs目录下,那么这个.so在apk被安装后会被放入/data/data/[packagename]/lib/这个目录下。Nativecode可以读取这个文件。这个方法我也验证过,是可行的,不过后来我还是用了方法c,如下:

c. 把资源文件转化为C++源码。文件中每个byte都变成char数组的一个值。读取时很简单,这些数组都已经作为静态常量载入到内存里,直接从内存解析就可以了。试下来100k左右的资源没有出问题。。(任然担心静态常量太大会不会出问题,后面再试试更大的数据)

2) S&R

众所周知手机会休眠,Android的app都遵循相同的lifecircle。



那么休眠对Ogles有影响么?答案是Texture、shader和bufferObject这些在显存中的对象都会丢失。

和以前D3D设备丢失几乎一样的性质。(好不容易逃出D3D9设备丢失的阴影,现在Android又来重蹈覆辙)所以在引擎在内存中备份了一份图像数据,用来恢复Texture。另外Shader、VertexBuffer和IndexBuffer都会在S&R时重新生成。

所以Graphic模块中的类设计为,只要握着纹理、着色器或是顶点缓冲对象,都会有OnResume方法来相应Activity的唤起。

下一篇为初级demo介绍
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: