一篇掌握JNI

NDK开发流程

  1. 在Java里面写native代码
  2. 在main目录下创建jni目录,写C代码—生成头文件
  3. 配置动态链接库的名称
  4. 加载动态链接库
   //把.so加载进来
   System.loadLibrary("hello");
  1. 使用

交叉编译

  1. 在一个平台上编译出在另外一个平台上可以运行的本地代码

  2. 平台CPU平台:x86、arm、mips

  3. 操作系统平台:Windows、Linux、Mac os、Unix

  4. NDK:模拟另外一个平台的特性进行编译

NDK压缩包下的文件

  1. sources:NDK相关的源代码

  2. platforms:根据不同的api-level 有多个文件夹 每个文件夹中都有不同CPU架构的文件夹

  3. include:跟Android下做jni开发相关的头文件

  4. lib:Google编译好的jni开发可能用到的函数库

  5. toolchains:交叉编译工具链

  6. build/tools .sh linux的批处理命令 通过这些批处理命令调用交叉编译工具链中的编译工具

  7. ndk-build.cmd:通过这个命令可以开启交叉编译的过程 如果想通过命令行来编译可以在Android上运行的本地代码 要吧ndk-build放到环境变量中
    在这里插入图片描述
    看到这个错说明配置成功了

JNI的理解

在这里插入图片描述
现在是Java是程序入口,所以C代码不用写main方法,只用写函数对native方法的具体实现

  cd /d:直接进入到后面的目录下

  cd /d E:\AndroidDemo\JNIDemo

JNI_Helloworld的步骤

  1. 首先新建一个NDKDemo的项目,我们在包名目录下建立一个调用C方法的类JNI
    在这里插入图片描述

  2. 给AS配置关联NDK

    1). local.properties中添加配置

    ndk.dir=G\:\\android-ndk-r10(=号后面为ndk的解压路径)
    

在这里插入图片描述
2). gradle.properties中添加配置
在这里插入图片描述
兼容老的ndk(老的版本):android.useDeprecatedNdk=true

	android.useDeprecatedNdk=true
  1. 在 JNI 中声明一个native方法,native方法不用实现
   //通过native关键字 声明了一个本地方法 本地方法不用实现,需要用jni调用C的代码来实现
   public native String helloInc();

native关键字表明该Java方法由非Java语言实现

最后类的样式如下:
在这里插入图片描述
我们为准备生成的so文件起名为 Hello ,JNI 会预加载该库,平时使用so库的小伙伴一定不会陌生了,这是一个标准的使用方法,说明我们的native方法来自于Hello.so库,其实JNI也是最后生成so库供以使用。

  1. 生成提供该方法的C类(可以先看这个:https://blog.csdn.net/weixin_42814000/article/details/105279704

    那么既然这样,我们就要用c语言实现一个同名方法用以调用,首先我们根据这个类生成一个c的头文件(这是c语言的内容如果不了解的话先照着做),在android studio下面terminal窗口执行如下命令:

    这里我遇到了第一个坑,先把正确实现的步骤写出来

生成头文件的方式

点击 Terminal 输入命令,便可以生成一个JNI的 C 头文件.

输入第二个命令行提示错误的原因是注释为中文。所以在输入命令行时不能出现注释或者中文
在这里插入图片描述

javac  -h  ./  JNI.java

上面的命令的作用:根据Java中的 native 方法生成对应在 C 中的方法该怎么写(自动生成)

这样为正确的,输入命令行没问题
在这里插入图片描述
这样会提示错误
在这里插入图片描述
下图为生成的头文件
在这里插入图片描述
JNI的头文件的代码如下
在这里插入图片描述

  坑1(绕路):有些资料给出的步骤是去/build/intermediates/classes目录下对.class文件执行javah,其实需要在生成头文件的文件夹下(cd app/src/main/java/com/lwm/ndkdemo)执行就可以。
 
  坑2(找不到类文件):有些资料给出的命令是 javah -d jni 包名.类型,在不同的本地环境下可能出现找不到类文件的提示,网上有人给出的解决方法是在classpath中做配置,其实直接使用javah -d jni -classpath 命令就可以了。
 
  坑3(JNI目录问题):注意JNI目录的位置是在/src/main之下,很多同学的JNI目录生成不对(比如使用了坑1方法生成再挪过来),位置错误导致最后运行时一直崩溃。

  1. 生成完头文件之后我们需要在写一个.c 文件并引用该头文件(还是c的内容,不了解的同学直接copy),在项目根目录下创建 jni 文件夹 在 jni 目录下创建.c 的代码(New–>c/c++SourceFile---->后缀选择.c),取名 Hello.c:
    在这里插入图片描述
    C函数命名规则: Java_包名_native方法所在类名_native方法名(JNIEnv* env,jobject jobj)
   //
   // Created by 林伟茂 on 2020/4/2.
   //
   #include <stdio.h>
   #include <stdlib.h>
   #include <jni.h>
   
   // 因为 sayHello() 方法返回的为String类型所以对应JNI中的jstring类型
   // 然后把.改为_,然后前面加一个 Java_
   /**
     jstring:返回值
     本地函数命名规则:Java_包名—_native函数所在类的类名_native方法名
     JNIEnv* env:相当于环境变量,里面有很多方法
     jobject jobj:谁调用了这个方法就是谁的实例
     当前就是 JNI.this
   */
   // 第一个参数 JNIEnv* JNIEnv是结构体 JNINativeInterface 这个结构体的一级指针
   // env又是JNIEnv的一级指针 那么 env就是 JNINativeInterface 的二级指针
   // 结构体 JNINativeInterface 定义了大量的函数指针,这些函数指针在 JNI 开发中十分常用
   // 第二个参数 jobject 就是调用当前 native 方法的 java 对象
   jstring Java_com_lwm_ndkdemo_JNI_sayHello(JNIEnv* env,jobject jobj){
   
   // jstring     (*NewStringUTF)(JNIEnv*, const char*);
       char* text = "I am from c";
       //通过 NewStringUTF方法把C的字符串转换成java的jstring类型
       return (*env)->NewStringUTF(env,text);
   }
  1. 在jni目录下创建一个Android.mk、Application.mk
    在这里插入图片描述
    Android.mk文件
   #makefile:作用就是向编译系统描述 我要编译的文件在什么位置 要生成的文件叫什么名字,是什么类型
   #call my-dir:获取当前的目录,因为放在jni这个目录,所以就找当前的目录
   LOCAL_PATH := $(call my-dir)
   #把上次编译的时候的信息清空,但是call my-dir里的内容不会被清除掉的
      include $(CLEAR_VARS)
   #在这里指定最后生成的文件叫什么名字
      LOCAL_MODULE := hello
   #要编译的C的代码的文件名
      LOCAL_SRC_FILES := hello.c
   #要生成的是一个动态链接库( 通过BUILD_SHARED_LIBRARY指定存储的扩展名为 .so)
      include $(BUILD_SHARED_LIBRARY)

Application.mk 文件

APP_ABI := all
APP_STL := stlport_static

在工程上app目录上右击选择link c++ project with Gradle,这个选项的意思是导入外部的c++工程,也就是把我们写好的c当做外部工程导入:
在这里插入图片描述
在下拉框中选择ndk-build,并选择对应的Android.mk文件的位置:
在这里插入图片描述
这里说明一下mk文件的作用,mk文件其实里面也就是一些 ndk 的文件路径配置、生成 so库名字、生成平台等的配置。

我们需要在 jni 目录里加入如上两个 mk 文件 Android.mk 和 Application.mk,如图是最基本的配置,解释下每个配置的注意的地方:

Android.mk:LOCAL_PATH 和两个 include 照写就行了,这三个配置 google 出来的意思比较生涩,解释也比较难懂,我们要关注的是 LOCAL_MUDLE 和 LOCAL_SRC_FILES,LOCAL_MUDLE 其实就是我们之前配置的 moduleName,指定了生成 so 库的名字,LOCAL_SRC_FILES则是我们引用的c文件位置。

Application.mk:APP_ABI 就是 abiFilters 了,所以之前我们做的配置都可以在这里写,赋值为 all 表明全平台生成,如果有多个用空格分开,APP_ABI:= armeabi-v7a armeabi。APP_STL指运行库类型,通常都是stlport_static,表示以静态链接方式使用的sttport版本的STL(写出这个配置翻译,是挺生涩的吧)。

项目build完之后,回想一下,我们选择的mk文件是 Android.mk,Applicaiton.mk 还没配置进去,打开app/build.gradle,你会发现多了一个配置:
在这里插入图片描述
所以Android.mk最后还是在gradle里面引入,其实我们就可以直接在build.gradle里加入这句引入代码,刚才用的方式是可视化界面的操作方式。接着我们在里面配置Application.mk:
在这里插入图片描述

externalNativeBuild {
            ndkBuild{
                // 指定 Application.mk 的路径
                arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk"
                //cFlags 和 cppFlags 是用来设置环境变量的,一般不需要动
                cFlags "-DTEST_C_FLAG1","-DTEST_C_FLAG2"
                cppFlags "-DTEST_CPP_FLAG2","-DTEST_CPP_FLAG2"
            }
        }

最后再build一下,终于大功告成了!
在这里插入图片描述
7. 通过ndk-build在项目根目录下编译.c文件 生成.so文件
在这里插入图片描述
如果生成了如上两个文件,并且ndkBuild文件夹下有对应so文件则说明成功了。

但是其实还有最后一个坑

我发现Application.mk中APP_ABI的配置并不起作用,于是在build.gradle中做了最后一步修改:
在这里插入图片描述
ndkBuild下生成的so文件也变了,终于ok了!
在这里插入图片描述
JNI 的 sayHello 方法已经可以调用了,我们在c里面让它返回一个 I am from c 的字符串,赶快试试吧!

  1. 调用.so之前需要使用System.loadLibrary来加载.so文件
   //把.so加载进来
   System.loadLibrary("hello");
  1. 调用 C 的函数
public class MainActivity extends AppCompatActivity {

    private Button cid;
    private String result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        result = new JNI().sayHello();
        System.out.println("result" + result);
        initView();
    }

    private void initView() {
        cid = (Button) findViewById(R.id.cid);
        cid.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "result=" + result, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

在这里插入图片描述
部分转载:https://www.jianshu.com/p/eae320ee9b2d

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章