Android:JNI编写步骤

JNI编写步骤
在已经配置好NDK开发环境之后,我们就可以进入JNI的开发了。

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

(1) 创建JNI目录
在这里插入图片描述

必须取名 ‘jni’
在这里插入图片描述

这个创建好的jni目录,主要就是存放c/cpp代码的目录。

(2) 代码的编写
java部分
在这里插入图片描述

在testApp/app/src/main/java/com/cpp/itcast/testapp/下创建一个java类。

这里取名为"HelloJni.java", 编写代码如下:

public class HelloJni {

/*改类是一个单例,(并不一定非得单例)*/
protected static HelloJni obj = null;    
public static HelloJni getInstance() {
    if (obj == null) {
        obj = new HelloJni();
    }       
    return obj;
}

//表示这个 helloJniJava接口,并不是java写的。是由比人提供的。
public native  void helloJniJava();

//加载cpp给提供的 动态库
static {
    System.loadLibrary("myJni"); //libmyJni.so
}

}
这里类中,我们看见两行陌生的代码:

//表示这个 helloJniJava接口,并不是java写的。是由比人提供的。
public native  void helloJniJava();

System.loadLibrary("myJni"); //libmyJni.so

这部分是java开发者写的,我们假定cpp最终会给java提供一个叫libmyJni.so的动态库,那么 System.loadLibrary(“myJni”); 就表示加载该动态库,记住该语句必须在static{}里面。

cpp部分
(1) 创建一个test.cpp文件
在刚刚创建好的jni目录下,创建一个test.cpp文件。

在里面先简单添加如下代码:

void hello_jni(void)
{

}
这就是一个空函数,什么功能都不写,现在我们要尝试将test.cpp生成一个libmyJni.so库。

(2)创建Android.mk文件
在刚刚创建好的jni目录下,创建一个Android.mk文件。
在这里插入图片描述

这是一个类似于Makefile的文件,主要用来编译jni下面的cpp代码。

里面添加代码:

LOCAL_PATH:=$(call my-dir)

include $(CLEAR_VARS)

#libmyJni.so
LOCAL_MODULE := myJni
LOCAL_SRC_FILES := test.cpp
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)
(3)编译so文件
cd 到 jni/ 目录下。执行ndk-build指令。

itcast:jni$ ndk-build
[arm64-v8a] Compile++ : myJni <= test.cpp
[arm64-v8a] StaticLibrary : libstdc++.a
[arm64-v8a] SharedLibrary : libmyJni.so
[arm64-v8a] Install : libmyJni.so => libs/arm64-v8a/libmyJni.so
[x86_64] Compile++ : myJni <= test.cpp
[x86_64] StaticLibrary : libstdc++.a
[x86_64] SharedLibrary : libmyJni.so
[x86_64] Install : libmyJni.so => libs/x86_64/libmyJni.so
[mips64] Compile++ : myJni <= test.cpp
[mips64] StaticLibrary : libstdc++.a
[mips64] SharedLibrary : libmyJni.so
[mips64] Install : libmyJni.so => libs/mips64/libmyJni.so
[armeabi-v7a] Compile++ thumb: myJni <= test.cpp
[armeabi-v7a] StaticLibrary : libstdc++.a
[armeabi-v7a] SharedLibrary : libmyJni.so
[armeabi-v7a] Install : libmyJni.so => libs/armeabi-v7a/libmyJni.so
[armeabi] Compile++ thumb: myJni <= test.cpp
[armeabi] StaticLibrary : libstdc++.a
[armeabi] SharedLibrary : libmyJni.so
[armeabi] Install : libmyJni.so => libs/armeabi/libmyJni.so
[x86] Compile++ : myJni <= test.cpp
[x86] StaticLibrary : libstdc++.a
[x86] SharedLibrary : libmyJni.so
[x86] Install : libmyJni.so => libs/x86/libmyJni.so
[mips] Compile++ : myJni <= test.cpp
[mips] StaticLibrary : libstdc++.a
[mips] SharedLibrary : libmyJni.so
[mips] Install : libmyJni.so => libs/mips/libmyJni.so
ndk-build会根据目前已经安装的那些工具链,而生成不同平台的编译结果。如果手机ARM处理器的,那么就是用armeabi编译的就可以了。
在这里插入图片描述

编译之后,我们会发现

会生成一个/libs/目录,里面会有各个平台对应的so文件。

我们需要让Android项目在生成app应用程序的时候,去关联这些so文件。

打开$项目/app/build.gradle

然后再android作用中添加如下代码

sourceSets {

 main.jniLibs {

     source {

         srcDirs = ['../libs']

     }

 }

}

externalNativeBuild {

 ndkBuild {

     path '../jni/Android.mk'

 }

}
(4)生成API的.h文件
现在是否可以再java文件中,直接调用我们在cpp中写好的void hello_jni(void)这个接口吗?

显然是不能的,因为java在定义接口有自己的命名规则和语法规则,所以我们需要对我们写好的hello_jni(void)函数做一层包裹函数。

那么这个包裹接口该怎么定义呢,这里我们可以利用一个工具javah来给我们自动生成这个包裹接口的定义原型。

cd /home/itcast/AndroidStudioProjects/testApp/app/src/main/java
javah -jni com.cpp.itcast.testapp.HelloJni
会生成一个com_cpp_itcast_testapp_HelloJni.h文件。

如下:

/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/
Header for class com_cpp_itcast_testapp_HelloJni */

#ifndef _Included_com_cpp_itcast_testapp_HelloJni
#define _Included_com_cpp_itcast_testapp_HelloJni
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_cpp_itcast_testapp_HelloJni
  • Method: helloJniJava
  • Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_cpp_itcast_testapp_HelloJni_helloJniJava
    (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
我们会看见javah会按照路径名字。给HelloJni类中,全部的native方法,生成一个包裹接口。

那么当我们在.java文件中,调用helloJniJava()方法的时候,实际上调用的就是 Java_com_cpp_itcast_testapp_HelloJni_helloJniJava()这个方法。 那么,接下来,我们只需要实现这个名字复杂的原型就ok了。

(5)实现包裹函数
再次打开test.cpp文件。

#include <jni.h>
#include <android/log.h>

void hello_jni(void)
{
__android_log_print(ANDROID_LOG_ERROR, “jnitag”, “hello jni is called!!!”);
}

#ifdef __cplusplus
extern “C” {
#endif

/*

  • Class: com_cpp_itcast_testapp_HelloJni
  • Method: helloJniJava
  • Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_cpp_itcast_testapp_HelloJni_helloJniJava
    (JNIEnv *, jobject)
    {
    hello_jni();
    }

#ifdef __cplusplus
}
#endif
写完之后,回到jni/目录

cd testapp/jni/

ndk-build
重新生成so文件。

(6)测试
打开MainActivity.java文件,在里面onCreate里面写一行测试代码。

    //test
    HelloJni.getInstance().helloJniJava();

之后编译运行。
在这里插入图片描述

发现,CPP封装的代码确实已经被java代码调用了,这就是jni的基本实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值