JNI是Sun公司定义的一套编程框架标准接口,允许Java代码和本地代码的相互调用.
我们什么情况下会使用JNI技术呢?
- 需要注重处理速度
- 直接进行硬件控制
- 对已有的本地代码进行复用
-加载动态链接库
我们通常接入别人sdk的时候都是使用的这种方法,比如接入新浪的SDK我们会得到一堆的动态链接库,我们需要将他们复制)到我们项目中调用,并将提供的jar文件添加到依赖库中
那如果我们自己来实现简单的本地方法和调用呢?
1.下载SDK Tools->NDK
在Setting->Android SDK->SDK Tools下下载NDK
2.声明本地函数
如果要在Java类中使用本地函数,那么我们需要现在Java类中声明它,声明本地函数时,需要在函数名前面添加关键字”native”关键字进行修饰,如下
package com.jju.yuxin.jnidev;
/**
* =============================================================================
* Copyright (c) 2016 yuxin All rights reserved.
* Packname com.jju.yuxin.jnidev
* Created by yuxin.
* Created time 2016/10/5 0005 上午 11:10.
* Version 1.0;
* Describe : 本地函数的声明
* History:
* ==============================================================================
*/
public class JniAddUtils {
public static native int add(int a,int b);
}
我们在项目中创建了一个叫JniAddUtils 的类,并在里面声明了一个本地方法add,我们准备用它来实现两个数相加的操作.(如果在这一步出现方法找不到之类的错误,我们可以到Setting->Plugins下将Android NDK Support后面的勾去掉,重启android studio就好了)
3.编译文件并生成头文件
我们知道在C这种调用别的文件的方法都是通过引入头文件的方式.在生成头文件之前我们需要先编译java文件让其生成.class文件,编译java文件我们只需要在点击编译项目就可以了.(点击运行项目按钮旁边的Make Project)
我们会在项目下的app\build\intermediates\classes\debug的包路径下找到生成的class文件(build是项目生成的临时文件夹,编译的东都在这个目录下),我们用android studio 下的Terminal cd到这个目录并使用javah -jni 路径的方式生成头文件
之后我们会在debug的目录下看到生成的头文件,如:
com_jju_yuxin_jnidev_JniAddUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jju_yuxin_jnidev_JniAddUtils */
#ifndef _Included_com_jju_yuxin_jnidev_JniAddUtils
#define _Included_com_jju_yuxin_jnidev_JniAddUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jju_yuxin_jnidev_JniAddUtils
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_jju_yuxin_jnidev_JniAddUtils_add
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
我们在mian下创建一个jni的文件夹,将之前的头文件剪切在这个文件夹中
4.实现本地函数
我们在 刚才创建的jni文件夹下创建一个.c文件用来实现加法运算,函数编写如下,正在编写函数时我们需要忘了将头文件引入,函数名为头文件中生成的函数名
jniadd.c
#include "com_jju_yuxin_jnidev_JniAddUtils.h"
//JNIEnv *, jclass是固定参数 jint, jint为传入的两个参数
JNIEXPORT jint JNICALL Java_com_jju_yuxin_jnidev_JniAddUtils_add
(JNIEnv * env, jclass thiz, jint a, jint b){
jint result=a+b;
return result;
}
其中JNIEnv * env, jclass thiz,
这两个参数是固定的参数,因为我们的方法在java中声明为static所以传入的是jclass,如果是非静态方法,那么传入的将是jobject,有人会问jint是什么东西?我们接下来来说一下,如果你知道了可以直接跳到下一步,
在本地函数的参数和返回值类型根据等价约定映射到Java语言中的数据类型
1.基本类型的映射
2.引用类型的映射
3.引用类型的继承关系
4.ndk路径的添加与配置
一般在新版的android stuido你下载完成ndk后他都会帮你在项目的local.properties添加NDK的路径,就像SDK一样,如果没有的话记得添加上去
,就像一开始一样我们要用jni我们先需要动态链接库(.so)所以我们需要在mudule的build.gradle中配置要生成的动态链接库的名称和类型(注意添加的位置,是在defaultConfig的里面)
ndk{
moduleName "AddJniLibName"//名字自拟
abiFilters "armeabi","armeabi-v7a","x86"
}
(如果后面运行成功,你会在app\build\intermediates\ndk\debug\lib\下看到三个目录下生成的对应的.so库)
5.代码中加载动态链接库
我们在最开始的JniAddUtils类中用到了本地方法,所以我们需要在那加载动态链接库
public class JniAddUtils {
static {
System.loadLibrary("AddJniLibName"); //加载动态链接库
}
public static native int add(int a,int b);
}
6.验证
我们在MainActivity中来验证下能否成功(布局就不写了,就一个TextView)
public class MainActivity extends Activity {
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_result = (TextView) findViewById(R.id.tv_result);
int addresult = JniAddUtils.add(1, 3);
tv_result.setText("1+3="+addresult);
}
}
截图:
7.日志的输出
在android代码中能够输出log日志,那怎么在C或C++中代码中输出log日志呢?
1.配置log库
如果你用的是正式版gradle,在ndk标签中加入
ldLibs “log”
如果你用的是实验版gradle,在ndk标签中加入:
ldLibs.add(“log”)
如果你使用CMakeLists,在target_link_libraries标签中加入log
如果你使用的是MK文件,加入如下语句:
LOCAL_LDLIBS := -llog
因为我使用的是Android Studio2.2正式版,那我需要修改ndk标签为
ndk{
moduleName "AddJniLibName"
abiFilters "armeabi","armeabi-v7a","x86"
ldLibs "log"
}
2.引入头文件
在你需要打印日志的C或C++文件中引入#include<android/log.h>
头文件.下面是我C文件修改版
#include <jni.h>
#include<android/log.h>
#include "com_jju_yuxin_jnidev_JniAddUtils.h"
//JNIEnv *, jclass是固定参数 jint, jint为传入的两个参数
JNIEXPORT jint JNICALL Java_com_jju_yuxin_jnidev_JniAddUtils_add
(JNIEnv * env, jclass thiz, jint a, jint b){
jint result=a+b;
//Log.i("JNIADD","result="+result);
__android_log_print(ANDROID_LOG_INFO,"JNIADD","result=%d",result);
return result;
}
其他几种日志输出为
ANDROID_LOG_INFO –> Log.i
ANDROID_LOG_WARN –> Log.w
ANDROID_LOG_DEBUG –> Log.d
ANDROID_LOG_ERROR –> Log.e
ANDROID_LOG_FATAL –> Log.a
8.遇到的问题
1.Error:Execution failed for task ‘:app:compileDebugNdk’.
Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set “$USE_DEPRECATED_NDK=true” in gradle.properties to continue using the current NDK integration.
这个错误需要在项目的gradle.properties下添加android.useDeprecatedNdk=true
2. connot delete “app\build\intermediates\classes\debug”
我的解决办法是重启android studio 因为每次重启,他都会重新的编译,也就会重新生成build文件,但是我局的这方法不怎么靠谱,不知道你们的能用不
以上就是我的测试后成功之谈,不当之处还望指出.
本文参考我同学的博文实现,有不清楚的地方,可以去他那看看.
在android studio 2.1 实现简单的ndk
我的博客网站:http://huyuxin.top/欢迎大家访问,评论.