在上一篇教程中,主要介绍了如何把OSG源代码编译成为能够在Android项目下使用的函数库。在这一篇教程中,我将针对如何在自己的Android项目中配置OSG函数库进行详细讲解。
现阶段网上关于OSGfor Android的配置方式教程有很多,但是大部分在实际使用起来都会或多或少的出现一些问题,无法完全照搬,需要一定的修改。而且,对于配置中的那些变量的具体含义,也很少有人能够进行仔细的讲解。这非常不利于新手的学习和理解,往往会造成出现bug后面对满屏幕的错误log完全一脸茫然的情况。
所以我将在这篇教程中详细介绍系统的介绍如何在Android项目中配置OSG函数库,并对各个配置变量的含义进行详细讲解。
-------------------------------------------------------------------------------------------------
在一个项目中配置函数库,是我们在进行项目开发时几乎都要做的第一件事情。可以说,各种第三方函数库帮助我们完成了很多通用的常用的功能,使我们从繁杂的基本算法、基本功能中脱离出来,能够更专心的完成我们需要实现的业务功能。OSG就是一个非常好的开源三维引擎,提供了一套成熟的API用于实现三维场景功能的开发。
一、工具准备
OSG是基于C++平台的API,在开发时使用的都是标准的C++。众所周知,开发Android项目,我们平常使用的都是Java,那么,如何才能在Java语言中调用OSG的这些C++函数呢?Java中提供了Java本地接口,即JNI(Java Native Interface)。JNI是一个协议,用于沟通Java代码和本地的C/C++代码。而Google公司为Android开发了一套用于快速创建native工程的工具集合,即NDK(Native Development Kit)。在本文中,将使用Eclipese+NDK的方式进行配置开发。网上有许多的经验教程用到了Cygwin。但是,从NDK的r7版本以上就包含了Cygwin,所以本文在配置项目时,并没有单独使用Cygwin。
这里使用的NDK版本是r10d,可以在官方网站上下载,链接为:
http://developer.android.com/tools/sdk/ndk/index.html。
当然,由于一些原因,现在很多情况下是连接不上这个网站的,比如我就是。不过现在很多网盘上有共享的可以下载,大家可以自行搜一下。OSG函数库可以根据我前一篇教程里讲的那样自己进行编译,不过时间花费比较大,当然也可以从网上下载别人已经编译好的OSG函数库,这样省时省力。这里我使用的是3.0.1版本的OSG。需要注意的是,上一篇教程讲过,在编译时根据参数不同,会有两个版本的OSG库,分别是GLES1和GLES2,这两个版本在使用时,配置的参数会有所不同,甚至会对新手来说产生一些莫名其妙的错误。具体的区别,我会详细的说明一下。
二、具体配置
首先,为了在项目中使用C/C++,在项目结构上会有一点不同,需要增加一个jni文件夹,其位置在项目的根目录下,与src等文件夹在同一层。这个文件夹用于存放所有的C/C++文件,以及项目配置文件(.mk后缀)。下面进行详细讲解。
1. 配置文件
在OSG for Android项目中,配置文件有两个,一个是Android.mk,另一个是Application.mk。这两个文件都是存放在jni文件夹内的,用于在编译项目时提供配置信息。
首先,我们来看Android.mk文件的配置方式。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := osgNativeLib
#OSG_ANDROID_DIR := D:/MyTools/osggles2_3_0_1
OSG_ANDROID_DIR := D:/MyTools/osggles1_3_0_1
LIBDIR := $(OSG_ANDROID_DIR)/obj/local/armeabi
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_ARM_NEON := true
LIBDIR := $(OSG_ANDROID_DIR)/obj/local/armeabi-v7a
endif
### Add all source file names to be included in lib separated by a whitespace
LOCAL_C_INCLUDES:= $(OSG_ANDROID_DIR)/include
LOCAL_CFLAGS := -fno-short-enums
LOCAL_CPPFLAGS := -DOSG_LIBRARY_STATIC
LOCAL_LDLIBS := -L $(SYSROOT)/usr/lib -llog
#LOCAL_LDLIBS += -L $(SYSROOT)/usr/lib -lGLESv2
LOCAL_LDLIBS += -L $(SYSROOT)/usr/lib -lGLESv1_CM
LOCAL_LDLIBS += -L $(SYSROOT)/usr/lib -lz
LOCAL_LDLIBS += -L $(SYSROOT)/usr/lib -gnustl_static
LOCAL_LDLIBS += -landroid
LOCAL_SRC_FILES :=osgMain.cpp osgNativeLib.cpp modelUtil.cpp AnimationUtil.cpp ExternVariables.cpp
LOCAL_LDFLAGS := -L $(LIBDIR) -losgdb_dds\
-losgdb_openflight\
-losgdb_tga\
-losgdb_rgb\
-losgdb_osgterrain\
-losgdb_osg\
-losgdb_ive\
-losgdb_deprecated_osgviewer\
-losgdb_deprecated_osgvolume\
-losgdb_deprecated_osgtext\
-losgdb_deprecated_osgterrain\
-losgdb_deprecated_osgsim\
-losgdb_deprecated_osgshadow\
-losgdb_deprecated_osgparticle\
-losgdb_deprecated_osgfx\
-losgdb_deprecated_osganimation\
-losgdb_deprecated_osg\
-losgdb_serializers_osgvolume\
-losgdb_serializers_osgtext\
-losgdb_serializers_osgterrain\
-losgdb_serializers_osgsim\
-losgdb_serializers_osgshadow\
-losgdb_serializers_osgparticle\
-losgdb_serializers_osgmanipulator\
-losgdb_serializers_osgfx\
-losgdb_serializers_osganimation\
-losgdb_serializers_osg\
-losgViewer\
-losgVolume\
-losgTerrain\
-losgText\
-losgShadow\
-losgSim\
-losgParticle\
-losgManipulator\
-losgGA\
-losgFX\
-losgDB\
-losgAnimation\
-losgUtil\
-losg\
-lOpenThreads\
-lgnustl_static\
-lgdal
include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_STATIC_LIBRARY)
LOCAL_PATH:这个变量用于在项目结构树中查找源文件。在这个例子中,对其配置为$(call my-dir),这是一个宏函数,由编译系统提供,表示返回当前路径,也就是包含了这个Android.mk文件的路径;
include $(CLEAR_VARS):这句配置的意思是清空之前的除了LOCAL_PATH以外的所有LOCAL_XXX的变量配置。可以看出,我们所需要配置的变量都必须在这条配置语句之后进行,不然即使配置了,也会被这句给清空掉;
LOCAL_MODULE:这个变量表示的是该配置文件描述的模块的名称,这个名称是唯一的。而且这个配置变量必须配置。在编译过程中,编译系统根据这个变量自动生成库动态库的名称,一般情况下,生成的动态库会以libxxx.so为名,其中的xxx就是LOCAL_MODULE变量配置时的名称;
OSG_ANDROID_DIR:这个配置变量表示的是OSG的所在位置,即经过编译的OSG的位置。在这个例子中,我写了两个路径,其中一个是被#注释掉的。之前也我提到过,OSG for Android在编译时根据参数不同,会产生两个版本,分别是GLES1和GLES2,这两个版本的OSG在配置时是不同的,这里就是不同点之一;
LIBDIR:这个配置变量是指向OSG函数库的位置,根据OSG_ANDROID_DIR所指向的OSG位置,向下寻找静态库的位置。其实,如果我们打开这个文件夹,我们可以发现一系列后缀为.a的文件,这些文件就是OSG的静态库。接下来的四行代码,则是用于判断目标ABI(即应用程序二进制接口),根据判断结果重新配置静态库位置;
LOCAL_C_INCLUDES:这个变量指向相应版本OSG的include文件夹,用于指定OSG头文件位置;
LOCAL_CFLAGS:这个变量是一个可选设置的变量,用于附加编译选项。在编写配置文件时,可以照搬此处的设置,无需修改;
LOCAL_CPPFLAGS:与上一个变量类似,区别是这个变量用于对cpp文件编译进行设置,从字面意思就可以看出,同上,可照搬。
LOCAL_LDLIBS:用于添加系统。注意!在配置这个变量时,其中存在对GLES1和GLES2两个版本配置的不同,这是第二个不同的地方。尤其需要注意的是,在选择使用GLES1版本的OSG时,该变量配置的是-lGLESv1_CM,而不是想当然的-lGLESv1!新手在刚接触时,很容易犯这个错,而且很难察觉到。其余照搬即可。
LOCAL_SRC_FILES:在这里列出需要编译进动态库的c和cpp文件。只要用到的c或cpp文件都要加进来,编译系统会根据这个变量的值来寻找文件,如果不加进来,编译系统就找不到相应的文件,就会造成编译错误。
LOCAL_LDFLAGS:这个配置变量与LOCAL_LDLIBS变量功能类似,也是用于添加系统库的功能。
include $(BUILD_SHARED_LIBRARY):这句代码的意思是将该模块定义为动态库,即.so文件。该例子中最后还有一句被#注释掉的include $(BUILD_STATIC_LIBRARY),这是指定该模块生成静态库,即.a文件。
下面我们再来看一下另一个配置文件,即Application.mk文件的配置方法。
APP_BUILD_SCRIPT := $(call my-dir)/Android.mk
APP_OPTIM := release
APP_PLATFORM := android-10
APP_STL := gnustl_static
APP_CPPFLAGS := -fexceptions -frtti
APP_ABI := armeabi armeabi-v7a
APP_MODULES := osgNativeLib
下面对各个变量进行详细讲解。
APP_BUILD_SCRIPT:这个变量将在当前路径下寻找Android.mk,也就是之前我们进行配置的mk文件;
APP_OPTIM:这是个可选变量,其值可以设置为release或者debug。当设置为release时,将会生成高度优化的二进制代码,而设置为debug时,生成的是未优化的二进制代码,但可以检测出很多的BUG,可以用于调试;
APP_PLATFORM:这个变量用于设置该项目的最小运行平台,需要注意的是,网上有很多教程,在这里设置的是android-8,但是根据我的测试,如果设置为android-8,会出现一些找不到文件的错误,但是设置为android-10以上,则可以正常编译;
APP_STL:这个变量设置为gnustl_static,表示使用GNU libstlc++作为静态库;
APP_CPPFLAGS:这个变量用于设置一个c++编译器开关集合,在编译任意模块的任意C或C++源代码时传递。它可以用于改变一个给定的应用程序需要依赖的模块的构建,而不是修改它自身的Android.mk文件;
APP_ABI:用于设置二进制程序接口,默认情况下为armeabi,本文中的例子同时设置了armeabi和armeabi-v7a。注意!网上有的例子里面在这里同时设置了x86,根据我的测试,加上x86后会产生NDK的Abortting stop的错误,所以,不要添加-x86;
APP_MODULES:这个变量列出编译所需要的模块名称。模块名称就是我们之前在Android.mk文件中设置的LOCAL_MODULE的值。
2. NDK Builder的配置
在eclipse左边的project view里右击需要设置的项目,并选择Properties,如图所示:进入后在界面左侧选择Builder,然后点击界面邮编的New按钮,如图所示:
在Choose configuration type的界面中选择Program,点击OK。如图所示:
在出现的设置界面中,在Main选项卡内,Location设置为所用的NDK的安装目录下的nkd-build.cmd;Working directory设置为当前工程。在Refresh选项卡内,勾选上Refresh resources upon completion。在Build Options选项卡中,勾选Specify working set of relevent resouces,同时点击Specify Resouces按钮,选择当前工程的jni文件夹。
3. Java中的配置
在Java中的配置比较简单,但是也是容易遗忘的地方。因为在Android上显示OSG窗口是基于GLSurfaceView的,那么在创建GLSurfaceView时需要注意的是,应该根据自己使用的OSG版本对GLSurfaceView的GL版本。这个配置只需要一句代码:
myGLSurfaceView.setEGLContextClientVersion(1);
因为我的例子中使用的是GLES1版本的OSG库,所以这里的值设置为1;同理,如果使用的是GLES2版本的OSG库,则将其设置为2。这里是比较容易遗忘的地方。
三、总结
据我了解,很多刚接触OSG for Android的新手老手,在配置项目时都会遇到这样或那样的问题,我在刚接触时同样是这样。出问题的大部分都是对这个配置的内容不是很了解其中的意思,所以会在出了问题后不知道该怎么办。我总结了一下容易出错的地方。
1. 乱加或乱减空格
在mk文件中,对空格似乎十分的敏感。我们很多人有意无意的会习惯性打空格,特别是在一行代码的末尾处,很容易多一个空格。但是这种情况在mk文件中都有可能引起错误。我在刚接触时,就试过逐行逐行地查看空格是否有多余或遗漏;
2. 在使用GLES1版本的库时,-lGLESv1_CM的错误
也许在看到一些使用GLES2版本的例子上写着-lGLES2时,会想当然的认为,使用GLES1版本就直接设置为-lGLES1就行了。结果编译时就会发现出错了。而且错误并没有直接指向这里,而是会出现一些找不到OSG头文件的错误,会让人很摸不着头脑。其实这种错误很大可能就是出现在了这里。
3. -x86的错误
我也看到过一些教程上面提到过,NDK在对x86的兼容上有一些问题,但是仍然有不少示例会加上-x86。这里我的建议是不要在Application.mk文件的APP_ABI后面添加-x86。
---------------------------------------------------------
OSG for Android新手教程的下一篇将对一个HelloWorld的示例进行详细讲解,通过一个简单的示例分析怎样运行起一个最简单的OSG for Android的程序。敬请关注。