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

安卓开发中学习使用ndk开发jni注意事项

2017-08-13 17:47 489 查看
最近在学习使用安卓的ndk开发遇到了一些问题,在这里记录下,免得再次踩坑,虽然谷歌官方推荐使用CMake编译,ndk即将废弃,但是依旧还是有很多人在使用ndk编译的,两种方式差别主要在于编译方式上,其他的大体相同,本着学习的态度,学习从ndk开始也是很有必要的,循序渐进,下面开始:环境:电脑系统 win7旗舰版   安卓工具 Androidstudio2.3.11.首先是环境的配置,ndk开发需要下载安装ndk(选装lldb,native调试工具,CMake谷歌新的native编译工具代替ndk)配置环境变量:添加环境变量  NDK_ROOT 为:   D:\Android\android-ndk-r10d在环境变量 PATH 下追加 :%NDK_ROOT%;NDK_ROOT即为ndk安装路径如图:ndk,lldb,cmake的安装可在安卓studio的sdk里面找到,勾选对应的选项后,点击apply,等待安装完成即可环境配置完成即可开始愉快的撸代码了,使用ndk编译一般有两种方式,这里先介绍第一种2.编写好Android.mk,application.mk,c/c++代码后通过在studio控制台或者cmd中输入ndk-build进行编译,编译成功后会生成对应CPU平台的so动态库文件,生成的文件一般在src\main\libs路径下,可以复制到jni文件路径下,当然也可以通过gradle来完成,或者指定资源路径2.1 编译时报错:NDK NDK_PROJECT_PATH=null .............解决:此种应该是studio识别你有c代码,去编译时找不到资源文件,其实此时我们的so已经编译好了,不需要studio再次编译,直接告诉他不用编译就可以了,在app的gradle中加入如下代码即可:
sourceSets {
main {
//            jniLibs.srcDirs = ['libs']
//            jniLibs.srcDir 'src/main/libs'
jni.srcDirs = []
jniLibs.srcDir 'src/main/libs'
}
2.2 studio检测到工程中包含代码,报了缺少一种编译方式(忘记记录错误详情了),来编译C代码解决:在project的gradle.properties中加入:
android.useDeprecatedNdk=true
即可
2.3 其他路径报错之类的请务必仔细检查ndk的安装路径,以及环境变量的配置是否正确,还有一些C里面的报错,
例如undefined char *...,检查头文件即可,等等这里就不再多说
3. 使用安卓studio自带的工具实现ndk编译
这个就比较简单了,不用再写Android.mk,以及application.mk文件,只需要编写好c/c++代码,然后在APP的
build.gradle的defaultConfig中配置如下设置:
ndk{
moduleName 'myJniTest'
abiFilter 'armeabi-v7a'
}
在project的gradle.properties中加入:android.useDeprecatedNdk=true
即可。
报错:一切就绪后,点击测试直接crash,报了native方法未implement..........
解决:经过自己检查发现是在创建c代码时,并未命名后缀,导致studio在编译动态库时不认识你写的C代码,所以
在编写C代码时一定要注意加上.c/.cpp的后缀
此种方式不用再提前在控制台编译出so库,编译成功后可以在app/build/intermediates/ndk/debug路径下找到
对应的so库以及.a库和自动生成的Android.mk文件
附上最终运行效果图一张(上一种方式效果相同):
4. 使用CMake插件编译C/C++代码
重头戏就是这个,也是必须把AS升级到2.2以后才能玩的功能,有了它写C代码更加方便
使用:在新建项目时,勾选Incude C++ Support,studio在构建项目时就会自动添加C支持以及生成一些实例代码
项目构建完成后会自动生成一些C代码,Java代码以及CMake配置文件,自动生成的build.gradle里面多出了这两个东西 :
android 里多了:
externalNativeBuild {cmake {path "CMakeLists.txt"}}
android的defaultConfig 里多了:
externalNativeBuild {cmake {cppFlags ""}}
在主 Mudule 的跟目录下多了个 CMakeLists.txt ,我们定制自己的原生代码的时候主要就是修改 CMakeLists.txt 里面的配置
在构建项目的最后一步会弹出这样一个界面,可以选择
这里有几项可选项对应解释如下:
C++ Standard:点击下拉框,可以选择标准 C++,或者选择默认 CMake 设置的 Toolchain Default 选项。Exceptions Support:如果你想使用有关 C++ 异常处理的支持,就勾选它。勾选之后,Android Studio 会在 module 层的 
build.gradle 文件中的 cppFlags 中添加 -fexcetions 标志。Runtime Type Information Support:如果你想支持 RTTI,那么就勾选它。勾选之后,Android Studio 会在 module 层的 
build.gradle 文件中的 cppFlags 中添加 -frtti 标志。
当 Android Studio 完成新项目创建后,打开 Project 面板,选择 Android 视图。Android Studio 会添加 cpp 和
External Build Files 目录
cpp 目录存放你所有 native code 的地方,包括源码,头文件,预编译项目等。对于新项目,Android Studio 创建了一个
C++ 模板文件:native-lib.cpp,并且将该文件放到了你的 app 模块的 src/main/cpp/ 目录下。这份模板代码提供了一个简答的
C++ 函数:stringFromJNI(),该函数返回一个字符串:”Hello from C++”。
External Build Files 目录是存放 CMake 或 ndk-build 构建脚本的地方。有点类似于 build.gradle 文件告诉 Gradle 如何编译你的
APP 一样,CMake 和 ndk-build 也需要一个脚本来告知如何编译你的 native library。对于一个新的项目,Android Studio
创建了一个 CMake 脚本:CMakeLists.txt,并且将其放到了你的 module 的根目录下。
例如:(以下例子为借鉴他人)
在原生代码里面添加本地库:这里以 log 库为例, log 库是 android 下的,如果我们新建项目的时候勾选上了 Incude C++ Support ,那么自动生成的
CMakeLists.txt 里面默认会为我们添加 log 库
下面是导入 log 库的头文件,并且宏定义 log 打印函数的代码:
#include <android/log.h>#define LOG_TAG "来自jni的日志:"#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
我们这里宏定义了个 LOG_TAG ,并宏定义打印函数 __android_log_print ,我们传入 ANDROID_LOG_ERROR ,
所以是E级别。
使用:
extern "C"jstringJava_com_technology_mr_sodemo2_MainActivity_msgFromJni(JNIEnv *env, jobject obj, jstring str) {const char *question = env->GetStringUTFChars(str, JNI_FALSE);char *answer = "**jni返回**";char *data = (char *) malloc(strlen(question) + strlen(answer)+1);strcpy(data,question);strcat(data, "JNI中");strcat(data, answer);LOGE("**jni中日志信息**");return env->NewStringUTF(data);}
当然我们平时玩 log 总是有一个总开关的,这里我们也可以定义一个,这个开关我们放在 build.gradle 里面:
externalNativeBuild {cmake {cppFlags "-DDebug"}}
-D 命令就是宏定义,这里我们宏定义了一个 Debug 。接下来我们在原生代码里面我们就可以根据是否定义了这个
宏来决定是否输出日志
实例:
 #include <jni.h>#include <stdio.h>#include <stdlib.h># ifdef Debug#include <android/log.h>#define LOG_TAG "来自jni的日志:"#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)# endifextern "C"jstringJava_com_technology_mr_sodemo2_MainActivity_msgFromJni(JNIEnv *env, jobject obj, jstring str) {const char *question = env->GetStringUTFChars(str, JNI_FALSE);char *answer = "**jni返回**";char *data = (char *) malloc(strlen(question) + strlen(answer)+1);strcpy(data,question);strcat(data, "JNI中");strcat(data, answer);#ifdef DebugLOGE("**jni中日志信息**");# endifreturn env->NewStringUTF(data);}
关于CMakeLists.txt,他是主要的配置的文件主要常用到三个方法:
add_library()
find_library()
target_link_libraries()
add_library :我们需要在里面指定三个东西,首先给 lib 取一个名字,然后指定作为动态库还是静态库,
最后指定源文件或者库。使用 add_library() 向您的 CMake 构建脚本添加源文件或库时, Android Studio
还会在您同步项目后在 Project 视图下显示关联的标头文件。不过,为了确保 CMake 可以在编译时定位您的
标头文件,您需要将 include_directories() 命令添加到 CMake 构建脚本中并指定标头的路径:
add_library(...)# Specifies a path to native header files.include_directories(src/main/cpp/include/)
find_library :将 find_library() 命令添加到您的 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。target_link_libraries :指定要关联到原生库的库,第一个自然是我们 add_library 里面指定的库名字 hi_jni 库,然后可以看到${log-lib} ,也就是引用了 find_library 里面定义的日志库。经过上面的脚本,基本可以玩起来了。引入其他编译好的静态库或者动态库我们需要在 add_library 里面把我们的库 add 进去,也是三个参数,指定库名字,指定库类型,第三个指定为 IMPORTED 关键字即可,然后我们需要再添加个 set_target_properties() 命令,里面 也是三个参数,要为其设置属性的库名称,指定其预构建类型,如 PROPERTIES IMPORTED_LOCATION ,最后指定 .a .so 库的绝对地址。总结:暂时就先写这么多吧,还在学习中,以上可能会有不正确的地方,后续再补充和更正
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android jni ndk