这里主要是写一个实例来解释JNI的使用。
设置NDK路径
选择File–Project Structure–SDK Location
或者也可以通过直接修改local.properties,在里面指定NDK的所在目录。两种方法都是一样的。
配置ndk属性
打开app下的build.gradle文件,在defaultConfig节点下增加属性配置
ndk{
moduleName "JniTest"
ldLibs "log","z","m"
abiFilters "armeabi","armeabi-v7a","x86"
}
以上配置代码指定的so库名称为JniTest,链接时使用到的库,对应android.mk文件中的LOCAL_LDLIBS,及最终输出指定三种abi体系结构下的so库。
配置gradle.properties文件
打开该文件,添加如下代码即可
android.useDeprecatedNdk=true
配置好,下面就可以开始写代码啦!
编写代码
建议:不在Activity中声明native方法,一是为了设计上的简洁并功能分离,二是创建头文件时避免不要的麻烦。
这里新建一个JniUtil类,声明一个native方法。
public class JniUtil {
static {
System.loadLibrary("JniTest");//加载so库
}
public native String getStringFromNative();//返回一句String
public native int add(int a, int b);//传int到C后两数相加
}
编写MainActivity文件,这里只有两个Button按键,一个点击后调用显示一个Toast显示从JNI传过来的文字。另一个点击后两个数字相加后返回显示。加载so库System.loadLibrary(“JniTest”);也可以不写在JniUtil上,写在MainActivity上,一样的。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_jni, btn_jni_int;
private JniUtil jniUtil;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
jniUtil = new JniUtil();
btn_jni = (Button) findViewById(R.id.btn_jni);
btn_jni.setOnClickListener(this);
btn_jni_int = (Button) findViewById(R.id.btn_jni_int);
btn_jni_int.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_jni:
Toast.makeText(MainActivity.this, "返回的String为:" + jniUtil.getStringFromNative(), Toast.LENGTH_SHORT).show();
break;
case R.id.btn_jni_int:
Toast.makeText(MainActivity.this, "和为:" + jniUtil.add(1, 3), Toast.LENGTH_SHORT).show();
break;
}
}
}
编写好代码后,执行Build–Make Project就会生成.class文件啦!
可以在app\build\intermediates\classes\debug\com\example\uidq0161\jnitest\MainActivity.class路径下找到生成的class文件。
然后打开Terminal终端
终端输入cd指令进入到app\src\main\java文件下,再输入javah指令。如下:
javah -d ../jni -classpath D:\android-sdk-windows\platforms\android-24\android.jar;D:\android-sdk-windows\extras\android\support\v4\android-support-v4.jar;D:\android-sdk-windows\extras\android\support\v7\appcompat\libs\android-support-v7-appcompat.jar;D:\AndroidStudioProjects\JNITest\app\build\intermediates\classes\debug com.example.uidq0161.jnitest.JniUtil
javah的完整命令很长。。主要格式为 javah –d –jni –classpath。
不熟悉javah指令的可以输入javah -help。
可以看到
-d < dir > 输出目录
路径介绍:
D:\android-sdk-windows\platforms\android-24\android.jar 为此Android工程所依赖的sdk版本,最终锁定到android.jar文件;
D:\android-sdk-windows\extras\android\support\v4\android-support-v4.jar代表support-v4包
D:\android-sdk-windows\extras\android\support\v7\appcompat\libs\android-support-v7-appcompat.jar代表support-v7包
D:\AndroidStudioProjects\JNITest\app\build\intermediates\classes\debug com.example.uidq0161.jnitest.JniUtil代表生成头文件所依据的.class文件。两个路径中间要用英文分号隔开。
执行完上面的指令就会生成一个jni文件夹并在该文件夹下生成一个.h文件,如下:
新建c文件(c文件里面的方法应对应jni下的.h文件中的方法名),代码如下:
#include <jni.h>
#include <com_example_uidq0161_jnitest_JniUtil.h>
JNIEXPORT jstring JNICALL Java_com_example_uidq0161_jnitest_JniUtil_getStringFromNative
(JNIEnv *env, jobject obj){
return (*env)->NewStringUTF(env,"HELLO FROM NATIVEJNI");
};
JNIEXPORT jint JNICALL Java_com_example_uidq0161_jnitest_JniUtil_add
(JNIEnv *env, jclass jobj, jint ja, jint jb) {
return ja + jb;
}
编译运行,可以在app/build/intermediates/ndk/debug/lib目录下看到so文件
效果图如下:
后面补充一点C代码回调java方法
* ① 找到字节码对象
* //jclass (FindClass)(JNIEnv, const char*);
* //第二个参数 要回调的java方法所在的类的路径 “com/itheima/callbackjava/JNI”
* ② 通过字节码对象找到方法对象
* //jmethodID (GetMethodID)(JNIEnv, jclass, const char*, const char*);
* 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名
* javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap
* ③ 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象调用创建的Method
* jobject obj =(*env)->AllocObject(env,claz);
* 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象
* 再通过这个对象来回调java的方法
* 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常
* ④ 反射调用java方法
* //void (CallVoidMethod)(JNIEnv, jobject, jmethodID, …);
* 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数