ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android NDK开发入门基础

2021-06-21 19:33:34  阅读:159  来源: 互联网

标签:NDK Java 入门 lib C++ library Android Native native


一、NDK/JNI

NDK
NDK(Native Development Kit)-原生开发工具包,使得能够在Android上
去使用C/C++代码;

JNI
JNI即Java Native Interface,Java和Native接口,就是Java和C/C++之间通讯的桥梁; 为什么要有JNI,因为Java和C/C++之间是无法直接相互调用的,也就是无法直接通讯,就和Java和JS之间也不能直接相互调用,中间需要翻译者来处理,JNI就是为了实现Java和C/C++之间这两种不同语言之间的通讯的;
JNI并不是Android中的技术,它是Java本身就具有的功能,是由JVM支持JNI功能

-那么为什么需要在Android上去使用C/C++代码?

因为我们知道开发Android我们是使用Java/Kotlin语言进行开发的,那么不论是Java还是Kotlin,最终都是编译成字节码文件.dex文件,然而字节码文件是Java/Kotlin经过一定语义编译之后的产物,并不是完全可以运行的机器码指令;所以运行应用时还是需要进一步的编译成机器码才能够执行;
而C/C++是完全的编译型语言,编译之后是机器码文件,运行应用时,可以直接执行的,所以效率肯定是要比Java/Kotlin要高的;所以一些对一些比较耗性能的操作,如音视频编解码,图像处理等操作,最好还是交由C/C++代码中去执行,这样能够大大提高应用的执行效率;

二、NDK开发环境配置

所需工具:使用AndroidStudio即可,但是需要增加下面两个工具NDK、CMake ;
NDK和CMake都可以在Android Studio 中的Tools/SDK Manager/SDK Tools下下载

在这里插入图片描述

CMake

CMake就是一个编译工具,可以根据提供的脚本来把C/C++代码编译成so库,和Gradle作用很类似,只不过CMake是用来编译本地语言(C/C++)的;

三、NDK开发

1.新建一个Native项目

Android Studio 新建一个项目,新建项目时,项目模板选择Native C++
新建项目成功后,我们可以看一下项目的目录结构;

在这里插入图片描述

在src/main目录下 有一个cpp目录,与java目录同级,cpp目录下有一个 CMakeLists.txt文件和native-lib.cpp
CMkeLists.txt就是CMake脚本文件
native-lib.cpp 是C++源文件

在这里插入图片描述
app Module build.gradle中会有一些NDk相关的配置

    android{

     defaultConfig {

       ...

        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    } 

     externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
      }

     }

2.设置调试模式

在Run/Edit Configuration/Debug Configurations中 Debugger 调试有四个选项
Auto、 自动检测
Java、 只调试Java代码
Native、 只调试Native代码
Dual、 Java+Native两种都用
注意不要选择Java和Native,选择Auto或者Dual

3.编写编译脚本文件CMakeLists.txt

CMake是一个跨平台的C++编译工具,所谓编译就是把C++代码编译成静态库或者动态库
CMakeLists.txt就是CMake的编译脚本文件,
CMake编译C++是通过CMakeLists.txt编译脚本来进行编译的;
CMakeLists.txt常用编译脚本

 (1)include_directories(dir_path) 

   //设置头文件的查找目录,可以使用多次include_directories设置多个头文件

                         //查找目录

   举例: include_directories(${CMAKE_CURRENT_SOURCE_DIR}/giflib)
   
   (2)add_library(编译生成的库名,

                  编译生成库的类型(STATIC/SHARED),

                  编译的库所使用的C/C++源文件(.c/.cpp文件)(可能会有很多) 
                  .
                  .
                  .

                  )  //指定编译生成库

      举例:add_library(

             native-lib

             SHARED

             native-lib.cpp)   
      
            上面这个add_library就指定了编译生成的库的名字为native-lib,库的类型为

            动态库,库所使用或者依赖的所以C/C++源文件只有一个native-lib.cpp

            这里编译native-lib库只使用了一个C/C++源文件native-lib.cpp,但是大多情况下,
            
            编译库,可能会使用依赖到很多C/C++源文件;               
            
            如:

            add_library(

             native-lib

             SHARED

             native-lib.cpp,

             a.cpp,

             b.cpp
             ....

             ) 

            这样把所有使用到的C/C++源文件一个个写进来是可以的,但是当非常多的时候,就很容易写漏,

            而且也很麻烦; 可以使用file() 这个脚本,来设置C/C++的查找路径的别名,最终使用这个查找路径

            别名,就可以替代写这个路径下的所有C/C++源文件了
            

    (3) file(GLOB 别名 某路径下某种类型文件) 查找某路径下所有某种类型文件
        
        举例:

        #设置查找GifLib库实现源文件(.c)文件目录
        file(GLOB giflib_C_DIR ${CMAKE_CURRENT_SOURCE_DIR}/giflib/*.c)

        #设置自己编写的C++实现源文件(.CPP)查找目录
        file(GLOB CSUTOM_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

        然后上面说到的add_library()设置编译生成库依赖很多C/C++源文件的是偶,就可以使用

        file()设置C/C++源文件查找别名

         add_library(

             native-lib

             SHARED

             ${CSUTOM_CPP_DIR}
             
             ${giflib_C_DIR}

             ) 

  (4) target_link_libraries(生成库的名字,

      生成库所链接的或者说是使用到的其他动态库或者静态库

      ) //设置生成库使用到的所有其他动态库或者静态库

      举例:
      
      target_link_libraries(
        
        native-lib

        a

        b
        ....
        
        )       
      
      生成库使用到了哪些其他动态库或者静态库都要写进来,如果很多的情况下一个个写就很麻烦了

      和add_library()写使用到的所有C/C++源文件一样,也可以借助file()来设置使用到的动态库或者

      静态库文件查找路径别名;

      #查找所有so库
      file(GLOB SO_DIR ../../../libs/${CMAKE_ANDROID_ARCH_ABI}/*.so)

      target_link_libraries( # Specifies the target library.

                       native-lib

                       ${SO_DIR}
                       
                        )

 (5) set(变量名  变量值) //用于定义一个变量指代后面的变量值

     比如但我们需要使用一个目录路径的时候,这个目录路径很长,并且在CMakeLists.txt中

     多个地方使用到,我们可以通过set()来定义一个变量指代这个目录路径,后面在使用到这个

     路径的时候,使用这个简单的变量来替代;
     
     set(LINK_DIR ../../../../libs/${CMAKE_ANDROID_ARCH_ABI})

     file(GLOB SO_DIR ${LINK_DIR}/*.so)


  (6)find_library(库的别名 库名) //从系统查找依赖库

      find_library( 
        log-lib
        log)

      find_library(
        jnigraphics-lib
        jnigraphics
      ) 

       target_link_libraries( # Specifies the target library.

                       native-lib

                       ${SO_DIR}

                       ${log-lib}
                       
                       ${jnigraphics}
                       
                        )

     log和jnigraphics都是NDK中自带的动态链接库

新建Native项目后,CMakeLists.txt默认已经有了一些一些基本配置,可以让我们正常编译使用native-lib.cpp了;

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.10.2)

#Declares and names the project.

project("nativedemo")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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 )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

4.加载C/C++库

Android 系统 API提供了两种加载C/C++库的方式:
一种是直接加载APK中的C/C++库文件;
第二种是通过文件地址来加载C/C++库文件;
(1)加载APK中的C/C++库文件
库文件可能是动态库so库,或者是静态库.a,或者是C/C++源文件
加载方式:

 static {
        System.loadLibrary("native-lib"); //注意不要写库文件扩展名
   }

(2)加载外部的C/C++ so库

如SD卡下的C/C++库 ,这里要注意的是,如果是加载外部的库文件,只能加载动态库so库,并且这个库文件的位置只能在/system/lib目录下,或者是在应用的安装包目录下/data/data/packagename/,
而且这两个路劲又是有权限保护的不能直接访问,所以一般我们是将库文件复制到我们应用自己的应用目录下/data/data/packagename/com.xxx.xxx

  System.load(" C/C++库文件的绝对路径(注意不需要写库文件扩展名)");

5.实现Java和C++互相调用

因为我们新建的项目模板是Native C++,所以项目中已经自动为我们创建了一个native-lib.cpp,C++源代码文件,MainActivtiy中也声明了一个native方法与native-lib.cpp中对应的native方法对应,并且在MainActivtiy中调用了native方法;
来演示Java中调用C/C++方法

在这里插入图片描述
在这里插入图片描述
下面我们来分析一下native方法:

Native方法中的包名要和Java方法中的包名对应上,Native层中JNI方法命名格式

为Java_包名类名方法名 之间全部是下划线分隔 :

Java_com_nado_jniproject_MainActivity_stringFromJNI

参数:JNIEnv *env和jobject thiz

这个jobject类型的thiz就是对应外层的Java对象 jobject就是Java中的Object

Java类型 C类型 以及 JNI别名 对应如下:

Java 类型	JNI 别名  	    C 类型
boolean	    jboolean	    unsigned char
byte	    jbyte	        signed char
char	    jchar	        unsigned short
short	    jshort	        short
int		    jint             int
long	    jlong	        long
float	    jfloat	        float
double	    jdouble	        double
String	    jstring	        char*
Class	    jclass	        /
Object	    jobject	        /

由于Java和C语言之间是无法直接调用的,两种语言的基本类型是不一样的,例如Java中有boolean类型,而在C中就没有这种类型,但是C语言还是有if-else判断的,那怎么判断true或者false? C中使用char类型,当char的值是0就是false非0就是true;基于这种情况,JNI重新定义了一些类型,以便C和Java对应上;

运行应用,我们可以在MainActivity中成功的显示从调用C/C++方法获取到的字符串

在这里插入图片描述

上面我们学习了Java中调用C/C++方法,为了清楚的了解Java和C之间的互相调用,还需要学习一下Native中调用Java方法

我们首先在MainActivity中定义一个Java方法,正常的定义就可以

public void callJavaMethod(String str) {

        Log.e(TAG, "callJavaMethod: "+str);

    }
然后在native-lib.cpp的方法中调用这个Java方法:

#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_nado_jniproject_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject thiz
        ) {

    std::string hello = "Hello from C++";

    jclass cls = env->GetObjectClass(thiz);

    jmethodID  methodID=env->GetMethodID(cls,"callJavaMethod","(Ljava/lang/

    String;)V");

    char* text="123";

    env->CallVoidMethod(thiz,methodID,env->NewStringUTF(text));

    return env->NewStringUTF(hello.c_str());
}

运行之后,可以看到logcat中成功打印出了如下内容,说明C/C++中成功调用了
Java中的方法

在这里插入图片描述

分析Native方法中调用Java方法:

    jclass cls = env->GetObjectClass(thiz);

    jmethodID  methodID=env->GetMethodID(cls,"callJavaMethod","(Ljava/lang/

    String;)V");

    char* text="123";

    env->CallVoidMethod(thiz,methodID,env->NewStringUTF(text));


  上面这一段代码就是Native方法调用Java代码的过程,这段代码很像Java中的反射调用对象的方法

   jclass cls = env->GetObjectClass(thiz); //这段代码就是获取Java对象的Class对象

   jmethodID  methodID=env->GetMethodID(cls,"callJavaMethod","(Ljava/lang/

    String;)V"); 

    //这段代码是为了获取Java类中的某个方法的;

      GetMethodID有三个参数,第一个参数就是jclass

      对象;

      第二个参数"callJavaMethod"就是Java中的方法名;

      第三个参数时方法的签名,方法的签名是对方法参数和返回值的描述;

      在Java中允许方法的重载,这个时候需要sign来进行区分;

      签名的书写规则: (参数类型签名)返回值类型签名

      数据类型	签名

      boolean	Z

      byte	    B

      char	    C

      short	    S

      int	    I

      long	    J

      float	    F

      double	D

      void	    V

      object	L开头,然后以/分割包的完整类型,再加;号,如Ljava/lang/String;

      Array	    以[开头,再加上数组元素的签名,比如int[],签名就是[I,Object数组签名就是
                [Ljava/lang/Object;

    //调用Java中的方法

    char* text="123";

    env->CallVoidMethod(thiz,methodID,env->NewStringUTF(text));  

Native方法的静态注册与动态注册

静态注册:

上面这种在Java中定义一个方法,然后在Native层定义一个Java_包名_类名_方法名(形参)的对应方法,这种属于Native方法的静态注册;

动态注册:

与之对应的还有一种Native方法的注册方式,动态注册;

动态注册的步骤:

(1) 首先还是和静态注册一样,在Java层定义一个native关键字修饰的方法

public native String stringFromJNI();     

(2) 在你的C++源文件中重写JNI_OnLoad方法

/**
 * 最终在Java层调用 stringFromJNI这个方法
   就会调用nativestringFromJNI到这个方法,
   因为,注册的时候, JNINativeMethod数组中
   stringFromJNI,传入的函数指针就是nativestringFromJNI
 */
jstring nativestringFromJNI(JNIEnv *env, jobject thiz) {
    char *str = "stringFromJNI";
    return env->NewStringUTF(str);
}

/**
 * Native方法的数组,动态注册Native方法,可以注册多个;
 *  JNINativeMethod是一个结构体,这个结构体里有三个内容
 *  方法名字:这里的名字就是Java层定义的Native方法的名字,一定保持一致
 *  方法签名:就是正常的函数签名 "()Ljava/lang/String;"
 *  一个函数指针:这里的函数指针就是当Java层调用stringFromJNI这个方法的时候
 *              最终就会调用Native层这个函数指针对应的方法  
 */
JNINativeMethod jniNativeMethods[] = {

        {
                "stringFromJNI",
                "()Ljava/lang/String;",
                (char *) nativestringFromJNI
        }
};

 extern "C"

 JNIEXPORT jint

 JNI_OnLoad(JavaVM *javaVm, void *) {

    JNIEnv *jniEnv = nullptr;

    javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);

    //Jaav层Native方法所在的类
    const char *mainActivityClsPackage = "com/example/jnidemo/MainActivity";

    jclass mainActivityClass = jniEnv->FindClass(mainActivityClsPackage);
    
    //jniNativeMethods就是数组的名字,也即一个指针
    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethods,
                            sizeof(jniNativeMethods) / sizeof(JNINativeMethod)
    );

    return JNI_VERSION_1_6;

    } 

JNI_OnLoad方法实际是你最终要生成的动态库的C++源文件中的一个方法,当你在Java层通过System.loadLibrary(“native-lib”)加载这个动态库的时候,这个时候JNI_OnLoad()方法就会被自动调用;
动态注册Native方法就是要在JNI_OnLoad这个方法通过JNIEnv来实现Java层定义的Native方法的动态注册;

以上就是Android NDK开发,Java和C/C++相互调用的过程;

标签:NDK,Java,入门,lib,C++,library,Android,Native,native
来源: https://blog.csdn.net/mq2856992713/article/details/118090046

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有