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:
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。
![](http://img.my.csdn.net/uploads/201212/16/1355664701_3987.jpg)
那么休眠对Ogles有影响么?答案是Texture、shader和bufferObject这些在显存中的对象都会丢失。
和以前D3D设备丢失几乎一样的性质。(好不容易逃出D3D9设备丢失的阴影,现在Android又来重蹈覆辙)所以在引擎在内存中备份了一份图像数据,用来恢复Texture。另外Shader、VertexBuffer和IndexBuffer都会在S&R时重新生成。
所以Graphic模块中的类设计为,只要握着纹理、着色器或是顶点缓冲对象,都会有OnResume方法来相应Activity的唤起。
下一篇为初级demo介绍
市面上支持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。
![](http://img.my.csdn.net/uploads/201212/16/1355664701_3987.jpg)
那么休眠对Ogles有影响么?答案是Texture、shader和bufferObject这些在显存中的对象都会丢失。
和以前D3D设备丢失几乎一样的性质。(好不容易逃出D3D9设备丢失的阴影,现在Android又来重蹈覆辙)所以在引擎在内存中备份了一份图像数据,用来恢复Texture。另外Shader、VertexBuffer和IndexBuffer都会在S&R时重新生成。
所以Graphic模块中的类设计为,只要握着纹理、着色器或是顶点缓冲对象,都会有OnResume方法来相应Activity的唤起。
下一篇为初级demo介绍
相关文章推荐
- GLES2 Graphic Engine Practice(五)iOS平台集成,SampleBrowser,GLES拾取
- 两分钟学会Android平台NDK编程(无须Eclipse和cygwin,可使用命令行打包多个so)
- Android平台NDK编程
- 两分钟学会Android平台NDK编程(无须Eclipse和cygwin,可使用命令行打包多个so)
- Android编程方法大PK:NDK vs. RenderScript
- Android: NDK编程入门笔记
- GLES2 Graphic Engine Practice(四)框架升级 & 第二阶段的6个demo
- 【winows7+android-ndk-r9+Cygwin 】cocos2dx 2.*游戏移植Android平台完全手册
- android_c++ 高级编程NDK学习笔记五
- Android: NDK编程入门笔记
- Android JNI&NDK编程小结及建议
- Android Studio Ndk 编程
- Cocos2D-X 项目发布到Android平台(二)配置安装Android SDK、NDK 及其相关工具
- Android: NDK编程入门笔记
- Windows环境下android平台native调试,从java debug 到 C++ (NDK DEBUG)
- [Android] Android平台的蓝牙编程
- Android: NDK编程入门笔记
- openGL as android graphic engine
- Android Studio JNI NDK编程(一)
- android 平台NDK MD5加密