举个栗子
- build.gradle设置
android { defaultConfig { ndk { moduleName "native-lib" // 指定库的名字 abiFilters "x86" // 如果不指定平台,AndroidStudio默认会生成所有平台的so } } externalNativeBuild { cmake { version "3.10.2" path "CMakeLists.txt" } } }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) add_library(native-lib SHARED src/main/cpp/native-lib.cpp ) find_library(log-lib log ) target_link_libraries(native-lib ${log-lib} )
- src/main/cpp/native-lib.cpp
可以使用java-h生成对应的头文件,
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnndk_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++!"; return env->NewStringUTF(hello.c_str()); }
将APK解压后发现在lib目录的确存在我们编写的c++库
编译自己的c++库
编译使用jni有两种构建方式,:
- CMake:Android studio新的构建方式,Project Path需要选择CMakeList.txt文件路径,jni会按照这个脚本来进行编译,具体脚本的编写看下面。
- ndk-build:老eclipse的构建方式,也就是Android.mk、Appli.mk的形式。
ndk-build
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := native-lib LOCAL_SRC_FILES := ../src/main/cpp/native-lib.cpp # include $(BUILD_SHARED_LIBRARY) # 编译为动态库 # include $(BUILD_STATIC_LIBRARY) # 编译为静态库
LOCAL_MODULE
:lib的名字
LOCAL_SRC_FILES
:编译的源代码
LOCAL_C_INCLUDES
: 头文件目录
LOCAL_SHARED_LIBRARIES
: 当前模块依赖的动态库模块
LOCAL_STATIC_LIBRARIES
: 当前模块依赖的静态库模块
PREBUILT_STATIC_LIBRARY
:用来指定一个预先已经编译好的静态库
PREBUILT_SHARED_LIBRARY
:用来指定一个预先已经编译好的动态库,与BUILD_SAHRED_LIBRARY
和BUILD_STATIC_LIBRARY
不同,该模块对应的LOCAL_SRC_FILES
不能是源文件,而只能是一个已经编译好的的动态库的路径,如foo/libfoo.so
- LOCAL_LDFLAGS := 静态动态都可以
Application.mk
项目级别的设置
- APP_STL: c++标准库stl
APP_STL := c++_shared # 会多出来一个libc++_shared.so APP_STL := c++_static
- APP_CPPFLAGS: 编译器选项
APP_CPPFLAGS := -std=c++11 # 支持c++11
常见错误
ld: error: undefined symbol: std::__ndk1::basic_string<char,
APP_STL
在Application.mk
中定义,不能在Android.mk
中定义,没有用
APP_STL := c++_shared # 动态库使用 APP_STL := c++_static # 静态库使用 APP_STL := gnustl_static # 低版本 静态库使用
项目之间相互引用
- settings.gradle
include ':Project1' project(':Project1').projectDir = new File(settingsDir, '../Project1')
- build.gradle
dependencies { compile project(":Project1") }
import-module
导入外部模块的.mk文件 ,和 include基本一样。
- include导入的是由我们自己写的.mk,路径是.mk文件的绝对路径
- import-module导入的是外部库、外部模块提供的.mk,路径是相对于
- NDK_MODULE_PATH中的路径列表的相对路径。
示例:
$(call import-module,相对路径)
NDK_MODULE_PATH
设置NDK_MODULEPATH的几种方法:
- 系统环境变量
- 直接将
NDK_MODULE_PATH=路径1:路径2
加到ndk-build
命令的参数后面,ndk-build
的参数最终会直接传给make
- 在import之前加入
$(call import-add-path,$(LOCAL_PATH))
,cocos2dx选择的是这种方式
Gradle Wrapper
Gradle Wrapper称为Gradle包装器,是对Gradle的一层包装。为什么需要Gradle Wrapper呢?比如在一个开发团队中,如果每进来一个成员,都需要在计算机中安装Gradle,这个时候运行Gradle的环境和版本就会对构建结果带来不确定性。针对这个问题,Gradle提供了一个解决方案,那就是Gradle Wrapper,它是一个脚本,可以在计算机没有安装Gradle的情况下运行Gradle构建,并且能够指定Gradle的版本,开发人员可以快速启动并运行Gradle项目,而不必手动安装,这样就标准化了项目,从而提高了开发效率。AS在新建项目时会自带Gradle Wrapper,这也是我们很少去单独去下载安装Gradle的原因。
执行:
gradle wrapper
这时会在项目根目录中生成如下文件:
├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew └── gradlew.bat
- gradle-wrapper.jar :包含Gradle运行时的逻辑代码。
- gradle-wrapper.properties :负责配置包装器运行时行为的属性文件,用来配置使用哪个版本的Gradle等属性。
- gradlew:Linux平台下,用于执行Gralde命令的包装器脚本。
- gradlew.bat:Windows平台下,用于执行Gralde命令的包装器脚本。