关于JNI,相比大家都不陌生,主要是最近工作用到了很多JNI相关的内容,踩了很多坑,记录下为了以后的学习者少走弯路.
一.先写一下Android studio NDK的下载和配置方式.
1.Ctrl+alt+S打开Settings面板
2.搜索界面输入SDK,之后选择SDK Tools
3.选择NDK工具点击OK下载即可.
点击NDK选中即可下载,建议把CMake一同下载
下载完需要配置NDK的路径
按住Ctrl+Shift+Alt+S打开Project Structure,选择SDK Location,选择下载的NDK-bundle的位置,如下图所示
二.javah和ndk-build的作用以及Android studio配置方式和linus指令集生成
1.javah命令的作用:生成java调用c++文件所需的.h头文件,头文件要符合JNI的规范 JAVA_包名类名方法名,具体实例如下
2.Android External Tools工具中配置javah命令和ndk-build命令
javah命令的配置方式及相关参数
打开Settings界面,选择ExternalTools工具,点击+号,如下图所示
Program:
Parameters: -classpath . -jni -o
Working Directory:
使用方式,在生成之前先选择build—make project编译下代码生成对应的.class字节码文件,选中要生成.h头文件的类,右键External Tools—javah 会弹出一个需要输入生成的文件的名字
生成之后的.h文件在jni目录下
之后就会在和java同级的目录下生成jni的文件夹,里面存放着.h头文件.
Android studio ndk-build指令生成.so文件的配置方式
Program配置的是ndk-build.cmd的安装目录
Working Directory指的是工作空间的目录
三.JNI中.mk文件的格式和作用.
在jni的目录下新建一个Android.mk文件,内容如下
LOCAL_PATH :=
LOCAL_MODULE := nativeTest
LOCAL_SRC_FILES := nativeTest.cpp
include
APP_MODULES := nativeTest
APP_ABI:= all
APP_ABI指的是生成的.so文件支持的CPU架构,指定All就是指生成所有的类型的CPU架构
生成后的libs文件如图所示
然后再写一下Android studio中调用.so,so文件的存放方式.可以调用成功的有两种配置方式
第一种就是在main目录下和java平级创建jniLibs文件夹,创建不同的CPU架构的文件夹,如下图所示:
第二种方式是把.so放入和src平级的libs目录下,但是要在gradle文件夹下配置一些命令,如图所示
gradule文件如下图所示
注意代码是添加在Android节点下面
代码如下:
sourceSets.main{
jni.srcDirs = []
jniLibs.srcDir “src/main/libs”
}
或者
sourceSets.main{
jniLIbs.srcDirs = [‘libs’]
}
这个代码配置的作用就是更改jniLibs目录指向libs.
主要是因为Android studio和之前的eclipse的目录结构的不同造成的.
javah指令集的调用方式,简单来说就是进入到java目录,找到确切的类执行javah命令具体如下
-d ../jni testjni.jnidemo.AndroidJNI(包名.类名)
下面讲解一个jni调用的完整demo,附带源码
1.先创建一个带有native方法的类 HelloWorld.class
package com.geocompass.sdk.map.jni;
/**
* Created by liuxu on 2016/12/28.
*/
public class HelloWorld {
public static native String stringFromJNI1();
}
然后用刚才配置的javah命令工具或者javah linux指令集生成.h头文件
build–make project 之后再运行javah命令,生成的HelloWorld.h如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_geocompass_sdk_map_jni_HelloWorld */
#ifndef _Included_com_geocompass_sdk_map_jni_HelloWorld
#define _Included_com_geocompass_sdk_map_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_geocompass_sdk_map_jni_HelloWorld
* Method: stringFromJNI1
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_geocompass_sdk_map_jni_HelloWorld_stringFromJNI1
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
注意生成的.h文件 方法名命名方式java–包名–类名–方法名的形式
本地方法的实现HelloWorld.cpp代码如下:
//
// Created by liuxu on 2016/12/28.
//
#include "HelloWorld.h"
#include <string.h>
extern "C"
JNIEXPORT jstring
JNICALL Java_com_geocompass_sdk_map_jni_HelloWorld_stringFromJNI1(
JNIEnv *env,
jobject obj) {
// std::string hello = "Hello from C++";
return env->NewStringUTF("Hello from C++");
}
Android.mk代码
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := HelloWorld.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk代码
APP_ABI:= all
注意.cpp文件要把.h包含进去,然后还要遵循JNI的规范,注意传入的数据类型,native方法是没有参数的,但是JNI的实现方法带有两个参数,第一个是个JNI引用的指针,第二个要注意是jclass还是jobject,之前我执行ndk.builid的时候一直报错,就是因为.h文件和.cpp文件的数据类型不一致造成的.
然后进入项目的jni文件夹下,如图所示
之后执行ndk-build指令,前提是你配置了ndk的环境变量,配置方式我就不多做阐述了.执行之后的效果如下
之后刷新下,会发现多了两个目录,libs和obj
然后把libs目录下的所有文件夹复制到jniLIbs文件夹下就可以了
java代码的调用代码如下
在activity中 通过静态代码块加载.so文件,之后就可以把c++的字符串通过jni调用显示到一个textview上面了
static {
System.loadLibrary(“test”);
}
tv1.setText(HelloWorld.stringFromJNI1());
Android相关部分比较简单就不过多阐述了.
希望我的demo对你有所提高.
不足之处请指教!直接在评论区提出你的问题即可,我们共同研讨