admin 管理员组

文章数量: 887186

1、so文件介绍
   “so”文件是使用C/C++编写生成的,在Android 平台上快速编译、打包该文件,它是一个动态链接库,而生成“so”文件其实就是JNI开发。

2、JNI开发简介
(1)、JNI简介
  JNI全称为Java Native Interface(JAVA本地调用)。从Java1.1开始,JNI成为java平台的一部分,它允许Java代码和其他语言写的代码(如C&C++)进行交互。并非从Android发布才引入JNI的概念的。
  JNI是一个协议,这个协议用来沟通java代码和外部的本地代码(c/c++),外部的c/c++代码也可以调用java代码。
(2)、JNI的作用

  • 效率上 C/C++是本地语言,比java更高效;
  • 代码移植,如果之前用C语言开发过模块,可以复用已经存在的c代码;
  • java反编译比C语言容易,一般加密算法都是用C语言编写,不容易被反编译。

(3)、JNI的数据类型
  Java的数据类型是跟C/C++的数据类型是不一样的,而JNI是处于Java和Native本地库(大部分是用C/C++写的)中间的一层,JNI对于两种不同的数据类型之间必须做一种转换,所以在JNI跟Java之间就会有数据类型的对应关系。
   JNI的数据类型包含两种:基本类型和引用类型。基本类型主要有jboolea、jchar、jbyte等,他们和Java中的数据类型的对应关系如下图所示:

  JNI中的引用类型主要有类、对象和数组,他们和Java中的引用类型的对应关系如下图所示:

(4)、JNI函数命名规则
  javah生成的c/c++头文件的时候,会对java中定义的 native 函数生成对应的jni层函数,如下:

    #include <cn_xinxing_jnitest_CalculateUtils.h> 
    JNIEXPORT jstring JNICALL  Java_cn_xinxing_jnitest_CalculateUtils_getStringFromNative
            (JNIEnv * env, jobject obj, jstring str){
            return str;
    }

   首先函数名的格式遵循如下规则:Java_包名_类名_方法名。比如

   Java_cn_xinxing_jnitest_CalculateUtils_getStringFromNative (JNIEnv * env, jobject obj, jstring str)

  其中cn_xinxing_jnitest是包名,CalculateUtils是类名,getStringFromNative是方法名,jstring 是方法的String类型的参数。JNIEXPORT、JNICALL、JNIEnv和jobject都是JNI标准中所定义的类型或者宏,它们的含义如下:

  • JNIEnv * :表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法;

  • jobject: 表示Java对象种的this;

  • JNIEXPORT和JNICALL: 它们是JNI中所定义的宏可以在jni.h这个头文件中查找到。

      如果不这样命名,当把动态库加载进DVM的时候,通过JNIEnv *指针去查找Java Native方法对应的JNI方法的时候,就会找不到了。

3、NDK的介绍
(1)、NDK的简介
  NDK全称为native development kit本地语言(C&C++)开发包。简单来说利用NDK,可以开发纯C&C++的代码,然后编译成库,让Java程序调用。NDK开发的可以称之为底层开发或者jni(java native interface)层开发。
   NDK是一系列工具的集合,NDK提供了一系列的工具,可以帮助开发者进行c/c++的开发,并能自动将.so打包成apk。NDK集成了交叉编译器,并提供了相应的mk文件可以做到隔离CPU,平台,ABI等差异,只需修改mk文件即可。开发人员只需要简单修改mk文件,就可以创建出“.so”文件。NDK还提供了一份稳定的功能有限的API头文件声明。

(2)、NDK和JNI的关系
  简单来说,NDK是工具,使用NDK可以更方便、快捷的开发JNI,NDK开发是基于JNI的。而JNI,是Java提出的协议,从Java1.1开始,JNI就已经是Java平台的一部分,它允许Java代码和其他语言写的代码进行交互,JNI开发便是基于此开发。
  使用NDK开发JNI的步骤如下;
① JNI接口的设计;
② 使用C/C++实现本地方法;
③ 使用NDK生成JNI动态链接库.so文件;
④ 将动态链接库复制到Java/Android工程,调用,运行即可。
⑤ so文件生成步骤
⑥ so文件调用

(3)、NDK的配置
  在android studio中下载NDK,然后在系统的环境变量中配置: 环境变量 PATH 下追加 :G:\AndroidSDK\ndk-bundle 这个表示NDK的位置。

4、so文件的生成步骤(有两种方法,这是方法一)
(1)、新建一个Android Studio 工程 JniTest,新建一个MyJni.java文件:

 public class MyJni {
    static {
        System.loadLibrary("MyJni");   //链接后面生成的MYJni.so库
    }
    public native static String getString();    //native 方法是C语言中要实现的方法
}

(2)、通过将java文件生成.class文件,然后编译成.h文件,这里使用了extend tool来实现一步生成.h文件。

配置javah和ndk-build: 在setting中选择extend tool,然后添加javah 命令的配置(一键生成h文件)
参数:
      Program:    $JDKPath$\bin\javah.exe 这里配置的是javah.exe的路径(基本一致)
                  $JDKPath$可以从右侧按钮(insert mac...)中选择
      Parametes:  $FileClass$ 这里指的是要编译.h文件的java类 (注意这里只填写了FileClass,其他参数没有导入那么自定义的一些model,或者Android.jar中的类是不支持的,可以先生成h然后手动输入特殊的参数。 比如Bitmap,在native方法中先不传这个参数,当生成h文件后,手动添加一个jobject的参数)
      Working:    ModuleFileDir\src\main\java //工作路径,也是.h生成的路径


  通过右键需要生成.h文件的MyJni 类然后找extend tool中的javah,点击就可以获得在Main文件夹下Java文件夹下jni文件夹下对应的.h文件,其中就包含了java中要实现的getString方法。
(2)、在jni文件夹下新建一个C语言程序:

  #include <stdio.h>
  #include <jni.h>
  #include <wu_com_ndktest_JNIUtils.h>
  JNIEXPORT jstring JNICALL Java_wu_com_ndktest_JNIUtils_getString
    (JNIEnv *env, jclass jclass){    //实现.h文件中的方法,返回字符串
      //返回一个字符串
          return (*env)->NewStringUTF(env,"This is my first NDK Application");
    }

(3)、在jni文件夹下新建Android.mk和Application.mk文件。
Android.mk:

LOCAL_PATH := $(call my-dir)           //固定写法,把路径赋给LOCAL_PATH变量
include $(CLEAR_VARS)                  //清除其他LOCAL变量
LOCAL_MODULE := JNITest             //这个模块的名字,最后生成的.so的名字就是它,要跟java里面的loadLibray的名字一样。
LOCAL_SRC_FILES :=test.c\     //这里是要编译的文件,\ 符号是换行,可以有多个
include $(BUILD_SHARED_LIBRARY)        //SHARED_LIBRARY就是动态库,即.so文件

Application.mk:

APP_ABI := all    / /生成在什么架构下的.so文件,all指所有架构都生成

(4)、在命令行下,cd到jni目录下,输入指令: ndk-build,等一会即可生成.so文件;当然也可以添加extend tool来实现。位于lib目录下,将其放到app/src/main/jniLibs目录下就能用了。

5、so文件的调用
  找到libs下生成的各体系结构下的.so库文件,然后把.so文件复制出来,放到需要使用的程序的Main的JAVA下新建的libs文件夹下:
(1)、把复制的so包,放到项目的libs目录下
(2)、在app module 下的buide.gradle 中添加下面代码:

//放在libs目录中
sourceSets {
main {
    jni.srcDirs=[]             //使用自己编写的两个mk文件,避免android studio想自动生成而导致错误。
    jniLibs.srcDirs = ['src/main/libs']        //libs表示libs文件夹的地址,一定要保证正确
   }
}

这样就可以在安卓中使用c语言或者c++语言了。

6、Android studio使用CMake生成”so”文件:
(1)、使用CMake构建native项目,使用CMake的话,必须要有CMakeLists.txt(注意名字不能写错),以及对应的cpp资源;在新建项目的时候把 include C++ support 勾选上就可以了,这样在MainActivity终就会生成对应的native方法对应Main文件夹下cpp文件夹的c或c++的文件方法,同样自己也可以定义需要的native方法,通过按alt+Enter就可以选择在c或c++文件中生成对应的方法,就可以在里面实现对应的代码逻辑。而CMakeLists.txt在App文件夹下,如果有需要的话,可以修改里面内容,CMakeLists.txt的内容如下:

# CMake最低版本 cmake_minimum_required(VERSION 3.4.1)
# 将需要打包的资源添加进来 
add_library( 
# 库名字 
native_hello 
# 库类型      表示编译生成的是动态链接库 
SHARED     
# 包含的cpp :表示编译文件的相对路径,这里可以是一个文件的路径也可以是多个文件的路径
native_hello.cpp ) 

# 链接到项目中 
target_link_libraries( 
native_hello 
android 
log )

(2)、当然上面介绍的是自动生成的c或c++,不过一般我们都是使用自定义的,这样才能符合自己的需求,方法如下:
AS已经提供了相对便捷的方法。首先在要使用jni调用的工程模块下新建一个

CMakeLists.txt:

代码:
cmake_minimum_required(VERSION 3.6)
add_library( # Sets the name of the library.
             xjni

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/jni/XJni.c )

find_library( # Sets the name of the path variable.
                log-lib

                # Specifies the name of the NDK library that
                # you want CMake to locate.
                log )
target_link_libraries( # Specifies the target library.
              xjni
              # Links the target library to the log library
              # included in the NDK.
              ${log-lib} )

  CMakeLists.txt具体配置上面已经说过了,这个地方是去掉了注释了的。需要注意的是,如果我们不在c源代码文件中输出日志到logcat,那么我们是不需要依赖log库的,也就是说find_library、target_link_libraries不是必须的。
  接着配置模块支持jni调用,对项目模块右键:

  在弹出的提示框中选择刚新建配置的CMakeLists.txt文件:

  看看app/build.gradle的变化:

  编译完成,编译器会报找不到XJni.c文件错误。ok,那我们新建一个/app/src/main/jni/XJni.c:
只有一行代码,ok,再编译,没问题!接下来新建jni调用java文件XJni.java:

   #include <jni.h>
    public class XJni {
        static {
            System.loadLibrary("xjni");
        }
        public native String getStr(String s);
    }

  如果getStr方法显示错误红色,不用着急,选中函数名,按快捷键alt+enter:
  选择create function后,函数就自动在XJni.c文件中生成了

#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_test_jnitest4_XJni_getStr(JNIEnv *env, jobject instance, jstring s_) {
    const char *s = (*env)->GetStringUTFChars(env, s_, 0);
    // TODO
    (*env)->ReleaseStringUTFChars(env, s_, s);

    return (*env)->NewStringUTF(env, returnValue);
}

  需要注意的是,最好让.java文件名与.c文件名同名,否则你可能快捷键不会出现create function选项。修改.c文件名的时候记得对应将CMakeLists.txt中修改。

最后这篇文章是借鉴了以下几位的心得:
https://blog.csdn/zxw136511485/article/details/53304258
https://blog.csdn/quwei3930921/article/details/78820991
还有不足,可以看这位大佬的引入第三方库:
https://blog.csdn/Xiongjiayo/article/details/85340121

本文标签: 文件 Android