很多C/C++代码以动态库的方式供第三方调用,在Unity中,这类文件(dll for windows, *.so file for Android/Linux, *.dylib for MAC OSX)叫插件。
在Unity开发的Android程序中使用动态库插件是非常方便的,曾经因为被误导而放弃使用unity,转而研究Android通过原生Java的JNI方式包装*.so文件,虽然还行,但是JNI晦涩丑陋的API实在看着不舒服。相对而言,C#也是可以直接包装*.so文件并在Android系统中直接调用的,而且Unity制作Android app跟Unity制作其他平台的app是无缝的,只是在发布时选择要发布的平台就行了,真正做到了平台无关,极大的方便了开发。
我还没仔细研究如何在Android Studio中开发NDK,这里只介绍如何用Eclipse开发NDK。
在Eclipse中开发NDK的几个必须的安装项:NDK编译环境、Android SDK直接从官网单独下载单独安装;CDT、ADT插件可以直接从Eclipse的help的插件管理中安装。
然后就可以创建项目了:
1、打开Eclipse,通过File->New Project,弹出对话框,填入项目名称:
2、点Next,出现Config Project窗口:
由于只用于编译NDK,所以把前两项的钩钩去掉,第三个勾上,标记为so项目;目录也自定义一下,因为通常情况下我只会为编译so文件配置项目,真正的代码会单独放在更顶层的目录,方便跨平台的其他编译项目使用。结果如下:
4、按Finish结束创建过程,目录结构如下:
5、添加Native代码支持。也就是JNI相关的东西:在左侧项目根目录上右键->Android Tools->Add Native Support…
在随后弹出的对话框中输入要生成的so文件的名字:
这时候会发现多了一个目录:
6、编译配置
在jni目录中加入Application.mk文件和Android.mk文件
Application.mk:
APP_ABI := all #APP_ABI := armeabi-v7a #APP_ABI += armeabi #APP_OPTIM := release APP_PLATFORM := android-8 #APP_BUILD_SCRIPT := Android.mk # GNU STL implements most C++11 features. Use either gnustl_static or gnustl_shared # Without this your C++ code will not be able to access headers like <thread>, <mutex> #APP_STL := stlport_static #APP_CPPFLAGS := -std=c++11 -frtti -fexceptions APP_STL := gnustl_static APP_CPPFLAGS := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG #-NDEBUG -mfpu=neon -fomit-frame-pointer -DULM_BLOCKED -msse3 -mfpmath=sse
Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := PNLib LOCAL_SRC_FILES := PNLib.cpp include $(BUILD_SHARED_LIBRARY)
本来Android.mk文件很简单,把所有头文件、cpp文件加入,直接编译就行。但是对于我的项目这远远不够,因为我的代码要跨平台,有各个平台的编译项目单独出去,同时使用一份src文件,所以代码被放到了顶层目录中的src目录下,我需要遍历这个目录并把它加入NDK编译系统中来,所以下面从网上找了一段遍历头文件和cpp文件的脚本来用了:
# Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := PNLib # 锟斤拷锟斤拷锟皆硷拷锟斤拷源锟侥硷拷目录锟斤拷源锟侥硷拷锟斤拷缀锟斤拷 MY_FILES_PATH := $(LOCAL_PATH)/../../src #$(warning $(MY_FILES_PATH)) MY_FILES_SUFFIX := %.cpp %.c # 递归遍历目录下的所有的文件 rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2)) # 获取相应的源文件 MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) ) MY_ALL_FILES := $(MY_ALL_FILES:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%) MY_SRC_LIST := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES)) MY_SRC_LIST := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%) # 去除字串的重复单词 define uniq = $(eval seen :=) $(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_))) ${seen} endef # 递归遍历获取所有目录 MY_ALL_DIRS := $(dir $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*/) ) ) MY_ALL_DIRS := $(call uniq,$(MY_ALL_DIRS)) # 赋值给NDK编译系统 LOCAL_SRC_FILES := $(MY_SRC_LIST) LOCAL_C_INCLUDES := $(MY_ALL_DIRS) # Add additional include directories LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../ #LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../Eigen-3.2.2 #必须从Android.mk配置文件中拿掉对Eigen的直接包含,放到程序代码中用相对路径包含: # #include "../../Eigen-3.2.2/Eigen" # using namespace Eigen; #$(warning $(LOCAL_SRC_FILES)) #$(warning $(LOCAL_C_INCLUDES)) # use log system in NDK LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
7、最后一步是为此项目配置一个NDK编译工具:
A、项目->右键->Properties,弹出项目属性对话框:
B、选中Builders,点击New按钮,弹出框中选中“Program”,点击OK:
C、出现新Builder配置对话框,随便起个名字“New_Builder_for_NDK”;
在Main标签页中,“Location”项,通过“Browse File System…“找到NDK的build文件,windows系统为ndk-build.cmd,Mac或其他类Linux系统为ndk-build;
工作目录”Working Directory“指定当前项目下的jni目录就行了。
D、切到Refresh页,勾选”Refresh resources upon completion“
E、切到”Build Options“页,勾选”Specify working set of relevant resources“,点击后面按钮”Specify Resources…“,指本项目下的jni目录。
最后的编译条如下:
点击左边锤子图标即可对项目进行编译。剩下的工作就是一步步修正跨平台代码,最后生成PNLib.so文件。
以上步骤有几个地方需要特别注意:
1、使用gun++11
NKD支持c++11,GCC已经支持大部分c++11特性,所以可以直接使用gun++11。实际上,如果用c++11,Android版程序可能报错:DLLNotFundException,编译出来的动态库在Android系统中无法加载,所以Application.mk文件中必须如下配置:
APP_STL := gnustl_static APP_CPPFLAGS := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG
2、x64版ndk目前还有bug
你的系统即便是x64的,也别下载x64版本的ndk,否则会报找不到make.exe的问题(make.exe不是内部命令XXXXXXXXX)
3、不识别vector、list等模板类的问题
Add
APP_STL := stlport_static
to their Application.mk file.
其实Android插件应该用APP_STL := gnustl_static
4、使用第三方库Eigen导致array等冲突的问题
原因是在NDK的Android.mk文件中把Eigen的路径也加入到里面了。
正确的做法是,不在Android.mk文件加入Eigen,而是在程序代码中直接包含Eigen头文件。