小艾笔记——JNI开发的问题总结

小艾同学虽然之前就写过关于JNI的相关的内容,但当时的项目的库文件是用的人家的,所以我只要了解就可以了。现在公司BOSS准备做JNI的开发了,让我先了解一下,所以重新学习了一遍,在这把遇到的问题整理一下,方便他人也方便自己。这里主要按照Android的JNI开发步骤介绍。

编写调用函数

新建自己的Android项目,建议把JNI的函数声明放在一个类里面,调用的时候也就是对象名 点 方法名!我这里就直接放在MainActivity中了(简单需要)。同时在项目的根目录下新建一个jni文件夹。这里我贴出了调用和声明的方法的具体内容
JNI_1
这里的调用格式一般是这样的,这一步是最简单的,一般不会出错。在环境会直接将java代码编译成.class文件,不在环境中写需要自行编译(后面用得着)。

生成共享库的头文件(.h文件)

生成主要.h文件主要依赖上一步Java文件编译生成的.class文件(依赖java源文件生成下文再说)。文件的路径如下图:
相关的路径

这里首先说一下这一步需要执行的命令:
javah -d (.h文件的生成目录 可以不写,默认当前目录下) -classpath (需要执行的类文件根目录 可以不写 代表就在当前class文件的包名根目录) -jni (一般是包名+类名 -jni可以不写 直接写包名+类名)

在我这里是这样的写的(供参考):
C:\Users\Mr.小艾\workspace\JNITest\bin>javah -d d:/ -classpath classes -jni com.xiaoai.jnitest.MainActivity

最简洁版:
C:\Users\Mr.小艾\workspace\JNITest\bin\calasses>javah com.xiaoai.jnitest.MainActivity

.h文件生成目录没什么好说的,一般会选择生成在上文新建的jni文件夹中。-classpath 有两种写法,一种是写绝对路径,不管你cmd在那个文件夹下执行该条语句,路径直接写到class文件包名的上一个目录,在我这里也就是到C:\Users\Mr.小艾\workspace\JNITest\bin\classes就可以了。 -jni 后面写的就是包名+类名;另一种是相对路径,就像我写的那样,因为在bin目录下执行该语句所以-classpath 只要写classes就可以了,-jni 还是一样的。路径的书写有个规律(自己发现的不知道是不是),执行命令的路径 + -classpath的路径 + -jni的路径 = 上文展示的 .class的全路径。
执行后运气好,会在jni目录下生成相应的.h文件。不然就会遇到这样的错误:
jni_error

如果不是这样的错误,就可能是路径、名称写错了,仔细检查一下。针对上面的错误,网上提供的网上大概分为两种:

一种是利用java的源文件

不用class文件生成.h文件。具体的实现是这样的
C:\Users\Mr.小艾\workspace\JNITest\src>javah com.xiaoai.jnitest.MainActivity
主要是路径的变化,在src文件夹下执行命令,其他的一样。我这里确实可以生成,结果是这样的:
java源文件生成的.h文件

一种还是利用class文件

这里需要对执行的命令加一些东西就可以执行了,具体的如下:
C:\Users\Mr.小艾\workspace\JNITest\bin>javah -d d:/ -classpath D:\Android\adt-bu
ndle-windows-x86-20140702\sdk\platforms\android-20\android.jar;classes -jni com.
xiaoai.jnitest.MainActivity

主要是在-classpath 和 classes之间添加了D:\Android\adt-bu
ndle-windows-x86-20140702\sdk\platforms\android-20\android.jar; 根据你项目的sdk版本,替换以上的内容。需要注意的是结尾的分号不要忘记。生成的结果是这样的:
class文件生成的.h文件
class文件生成的.h文件

两种方法生成的结果,我这里都贴出来了。网上有人说两种方法生成的结果是一样的,我不知道有没有实际的运行?很明显,结果单从代码多少就是不一样的!可能他们的意思是后面的使用结果是一样的,的确,两个代码最后生成的库文件是一样的,也不影响后面的使用。当然两种方式我更加看重利用class文件的生成方式。

根据生成的.h文件,编写相应的C/C++源文件

在项目的jni文件夹下新建.c或者.cpp文件。文件名是和.h文件名一样。我承认c++不是我的强项,函数主要实现了返回字符串,这里直接贴出主要的代码,供参考:

#include<jni.h>
/* 本地接口,它将在java代码中调用 */
JNIEXPORT jstring JNICALL Java_com_xiaoai_jnitest_MainActivity_printJNI(JNIEnv *env, jobject obj,jstring inputStr)
{
  // 从 instring 字符串取得指向字符串 UTF 编码的指针
  const char *str =
  (const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE );
  // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
  (*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str );
  return (*env)->NewStringUTF(env, "Hello xiaoai");
}

/* 该函数将在库首次加载时调用
*  你可以在库文件中做一些初始化 例如注册函数映射表,缓存一些变量等
*  最后返回当前环境所支持的JNI环境
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
  void *venv;
  if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
    return -1;
  }
  return JNI_VERSION_1_4;
}

在c/c++代码中可以对函数名进行映射,也就是简化函数名。将映射函数的执行放在JNI_OnLoad主函数中。

生成.so库文件

编写好c/c++源文件后,还需要在jni文件夹新建一个android.mk文件。这时项目的文件结构如下:
文件结构

Android.mk文件是在使用NDK编译C代码时必须的文件,Android.mk文件中描述了哪些C文件将被编译且指明了如何编译。Android.mk文件内容如下:

#编译时的目录
LOCAL_PATH:= $(call my-dir)
# 下面是一个完整模块编译
#清除之前的一些系统变量
include $(CLEAR_VARS)
#编译的源文件
LOCAL_SRC_FILES:=com_xiaoai_jnitest_MainActivity.c
#需要包含的头文件目录
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
#编译生成的目标对象(系统会自动在前面加上 lib 在后面加上 .so ,如果名字已经有前面已经有lib就不会加lib 。最终的生成的文件是 libMainActivity.so)
LOCAL_MODULE := MainActivityJni
#链接需要的外部库
#LOCAL_SHARED_LIBRARIES := libutils
#是否需要prelink处理
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
#指明要编译成动态库
include $(BUILD_SHARED_LIBRARY)

当然做过NDK开发的同学应该都知道有个Application.mk文件,这是android NDK构建系统使用的一个可选构建文件。它的目的是描述应用程序需要哪些模块,也定义了所有模块的一些通用变量。主要有以下几个变量。在系统的根目录新建Application.mk(当然这个是可选的)。内容如下:

APP_PROJECT_PATH := $(call my-dir)
APP_MODULES := MainActivityJni

本文不详细介绍这两个.mk文件的相关配置,打算端午之后系统学习一下在写一篇关于.mk文件的博客。请继续关注。

到这里基本就快大功告成了,就差临门一脚了,千万不能怂啊。
首先需要配置NDK环境,在NDK官网上下载自己电脑相应的软件包NDK下载地址 解压之后放到一个目录下,之后配置环境变量(和Java差不多,新建一个系统变量NDK_HOME,保存ndk的路径,然后在path最后添加)。完成之后在命令行输入ndk-build,如果有反应就说明配置成功了。没有还要努力啊。。。
之后再自己的工程的根目录下执行 ndk-build 就可以生成.so文件了
ndk执行命令

最终结果

这一步遇到的问题有:Android.mk文件必须存在,而且内容不可以有错;生成的.so文件有很多都放在相应的文件夹里面,我项目运行的时候说找不到,最后选择其中的一个copy到libs的根目录就可以了;还有就是引用的库名写错了,按照android.mk文件来就可以了,不用自己加上lib。引用的代码如下:

System.loadLibrary("MainActivityJni");

调用如下:
在设置布局的代码下加上
System.out.println(“——–>”+printJNI(“xiaoai”));
运行之后再logcat输出c++函数返回的字符串,输出 Hello xiaoai

总结

到此,jni的简单运行就结束了。其实很简单,细心一点就可以。基本上会遇到的问题也就是一下这些吧?毕竟一些我自己也没有遇到,有问题的话欢迎交流。我是Mr.小艾。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值