精华内容
下载资源
问答
  • 个人认为使用Android Studio作NDK开发是必然趋势,所以本文将简单介绍如何在Android Studio上实现NDK开发。 简介 JNI JNI 是Java Native Inteface的缩写,是Java中定义的一种用于连接Java和C/C++接口的一种实现方式...
  • Android NDK开发及Sqlite3加密技术 张伟伟 2012/07/12 Android NDK开发 什么是NDK NDKNative Development Kit 1NDK是一系列工具的集合 NDK提供了一系列的工具帮助开发者快速开发C(或C++) 的动态库 NDK集成了交叉...
  • 在AS中进行NDK开发之前,我们先来简单的介绍几个大家都容易搞懵的概念:  1. 到底什么是JNI,什么是NDK?  2. 何为“交叉编译”?  先看什么是JNI?JNI的全称就是Java Native Interface,即java本地开发接口。...
  • 《Android NDK 开发教程 - v1.0》
  • Android NDK 开发教程

    2021-01-05 09:58:25
    Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。 众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序...
  • NDK开发教程

    2018-05-08 13:55:48
    NDK开发 详细开发教程 NDK开发 详细开发教程 NDK开发 详细开发教程
  • android NDK 开发指南

    2018-04-04 20:31:34
    android NDK 开发指南 android NDK 开发指南 android NDK 开发指南 android NDK 开发指南
  • 这算是尚硅谷培训学校推出的Android视频教程的高级教程了吧,而本套教程正是在Android开发中的核心重点开发技术(JNI/NDK...本教程照顾了不会C/C++的同学,会讲一些C/C++的基本知识,是一个JNI/NDK开发不可多得的教程。
  • Android之NDK开发入门

    千次阅读 2020-05-11 14:21:51
    NDK全称Native Development Kit,是Android的一个工具开发包,能够快速开发C,C++的动态库,并自动将so和应用打包成APK。而NDK的使用场景就是通过NDK在Android中使用JNI,那么JNI又是啥呢?JNI全称是Java Native ...

    注意:本文操作环境为mac,Android Studio版本3.5

    前言

    NDK全称Native Development Kit,是Android的一个工具开发包,能够快速开发C,C++的动态库,并自动将so和应用打包成APK。而NDK的使用场景就是通过NDK在Android中使用JNI,那么JNI又是啥呢?JNI全称是Java Native Interface,即Java的本地接口,JNI可以使得Java与C,C++语言进行交互。这么一来,通过NDK和JNI,就可以很方便的在Android的开发环境中使用c,c++的开源库。

    一、安装和配置NDK

    1.安装NDK

    可通过Android Studio下载和官网下载,下面为Android Studio下载

    1. 打开Android Studio,点击Android Studio->Preferences,搜索SDK,然后在SDK Tools中勾选LLDB,NDK,Cmke进行下载。其中LLDB是调试本地代码的工具,可调试C++代码在这里插入图片描述
    2. 打开File->Project Structure,然后在SDK Location中配置NDK路径,点击右下角就会出现我们刚刚下载的NDK路径,点击Default NDK路径即可,如果是在官网中下载的,可以根据自己下载的路径进行配置
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGku4a3U-1589177307895)(/Users/jaceyuan/md/博客/Android:NDK开发/2.png)]2.配置NDK环境变量

    2.配置NDK环境

    1. 启动终端,进入当前用户的home目录

      cd ~(注意中间的空格)
      
    2. 创建.bash_profile(假如之前已经创建好了,执行这个命令行不会对原本的文件内容造成影响)

      终端输入:touch .bash_profile
      
    3. 查看、编辑.bash_profile

      如果忘记了NDK的目录,可以通过Android Studio的File->Project Structure中的NDK location查看

      export NDK_ROOT=/Users/{你的用户名}/Library/Android/sdk/ndk-bundle
      export PATH=$PATH:$NDK_ROOT
      
    4. 保存然后关闭.bash_profile文件

    5. 更新刚配置的环境变量

      终端输入: source .bash_profile
      
    6. 重新打开终端,检查是否配置成功(如果不成功,记得先关闭当前终端然后打开)

      终端输入: ndk-build
      

      如果出现下列结果的即为成功

      Android NDK: Could not find application project directory !
      Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
      

    二、CMake的方式编译生成so库

    1. Android Studio自动生成的示例

    1.1 新建Native C++工程

    在Android Studio新建一个Native C++的工程,然后填写项目名,并选择Toolchain Default使用默认的C++标准。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eyt77dA2-1589177307901)(/Users/jaceyuan/md/博客/Android:NDK开发/3.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15bP6O2A-1589177307903)(/Users/jaceyuan/md/博客/Android:NDK开发/4.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u7w2KQw5-1589177307904)(/Users/jaceyuan/md/博客/Android:NDK开发/5.png)]

    1.2 分析AS创建和添加的文件

    当点击Finish后,Android Studio会自动添加NDK开发相关的文件。cpp是AS帮我们自动生成的,里面有两个文件:

    • CMakeLists.text:构建脚本
    • Native-lib.cpp:示例C++源文件

    另外还对MainActivity,build.gradle进行了一些改动。下面将分析这四个重要的文件

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elYNgbq4-1589177307905)(/Users/jaceyuan/md/博客/Android:NDK开发/6.png)]

    1. CMakeLists.txt

      cmake_minimum_required(VERSION 3.4.1)
      # 这里会把 native-lib.cpp转换成共享库,并命名为 native-lib
      add_library( # 库的名字
              native-lib
      
              # 设置成共享库
              SHARED
      
              # 库的源文件(由于native-lib.app和CMakeLists.txt同处于一个包,因此可以直接写文件名
              # 不然的话需要写成src/main/cpp/native-lib.cpp)
              native-lib.cpp)
      
      # 如果需要使用第三方库,可以使用 find-library来找到
      find_library( # so库的变量路径名字,在关联的时候使用
              log-lib
      
              # 你需要关联的so名字
              log)
      
      # 通过link将源文件的库和第三方库添加进来
      target_link_libraries( 
              # 源文件库的名字
              native-lib
      
              # 添加第三方库的变量名
              ${log-lib})
      

      需要注意的是第三库是路径变量名,因此需要用${}方式引用

    2. Native-lib.cpp

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khciEW8X-1589177307906)(/Users/jaceyuan/md/博客/Android:NDK开发/7.png)]

      一看就是C++的代码,这个方法的作用其实就是返回一个字符串“Hello from C++”。需要重点注意的是这个方法的命名格式,包名,类名,方法名其实就是在Java代码中定义这个native方法stringFromJNI所在的包名和类名。

    3. MainActivity

      public class MainActivity extends AppCompatActivity {
      
          //加载so库
          static {
              System.loadLibrary("native-lib");
          }
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              
              TextView tv = findViewById(R.id.sample_text);
              //直接调用native方法
              tv.setText(stringFromJNI());
          }
      
          //native方法
          public native String stringFromJNI();
      }
      

      在这里我们验证了native-lib.cpp里面方法的命名格式,在MainActivity确实有一个stringFromJNI的方法。在这里我们首先需要加载so库,so库的名称就是我们在CmakeList.txt定义的库的名字。然后通过定义的native方法就可以调用C++层的Java_com_example_ndkdemo_MainActivity_stringFromJNI方法。

    4. build.gradle

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibQ34NaX-1589177307906)(/Users/jaceyuan/md/博客/Android:NDK开发/8.png)]

      看到这,你可以先运行下,看看AS自动生成的JNI例子是否运行成功了。想必当你看到成功运行后是不是已经热血沸腾,迫不及待的想自己尝试下。并且一个so库中不可能只有一个方法,因此接下来就让我们照猫画虎的添加自己编写的c++文件。

    2. 自己编写的so库

    1. 创建Java对应的加载类。在这里我们不准备在MainActivity中加载.so库,而是新建了一个JNI工具类来完成加载.so库和声明native方法的任务。然后将MainActivity中的native方法复制过来,并且新建了一个helloFromJNI的方法。另外为了在新项目中使用该so库,我们将so库的名字更改为hello,下面也会在CMakeList.txt中更改so库的名称。(可以发现下面的native方法是红色的,这是因为我们还没有在C++层中实现这两个方法)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6HCgDEtR-1589177307907)(/Users/jaceyuan/md/博客/Android:NDK开发/9.png)]

    2. 添加需要的C/C++文件。我们直接在cpp中新建一个就行,cpp->右键->new->c/c++ source File。然后就可以命名一个c/c++文件了,并且勾选create an associated header,表示在创建才C/C++文件的同时会创建对应的头文件。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBQA13iV-1589177307907)(/Users/jaceyuan/md/博客/Android:NDK开发/10.png)]

      (1) 编写头文件hello.h,你可以将头文件看成Java的接口,在这里我们需要声明方法。

      #ifndef NDKDEMO_HELLO_H
      #define NDKDEMO_HELLO_H
      
      //声明接口
      extern const char* helloWorld();
      #endif //NDKDEMO_HELLO_H
      

      (2) 然后在hello.cpp中实现这个头文件,可以发现在这里我们只是简单的返回了一个hello world的字符串

      #include "hello.h"
      extern const char* helloWorld(){
          return "Hello World";
      }
      
    3. 在native-lib中引入hello.h头文件。这个操作跟Java中的导包有点类似,并且我们新建了一个在前面JniUtil中声明的native方法,注意的是由于我们将加载so库和声明native方法都放到了JniUtil中,因此我们需要更改之前stringFromJNI的包名和方法名。

      #include <jni.h>
      #include <string>
      #include "hello.h"
      
      extern "C" JNIEXPORT jstring JNICALL
      Java_com_example_ndkdemo_util_JniUtil_stringFromJNI(
              JNIEnv *env,
              jobject /* this */) {
          std::string hello = "Hello from C++";
          return env->NewStringUTF(hello.c_str());
      }
      
      
      extern "C" JNIEXPORT jstring JNICALL
      Java_com_example_ndkdemo_util_JniUtil_helloFromJNI(
              JNIEnv *env,
              jobject /* this */) {
          std::string helloStr = helloWorld();
          return env->NewStringUTF(helloStr.c_str());
      }
      
    4. CMakeList.txt中加入hello.cpp的路径添加

      这里需要注意的是,如果是多次使用add_library,则会生成多个so库。在这里我们只是将多个本地文件编译到一个so库中,因此只需要在原本的add_library中添加hello的相对路径。并且为了方便在新项目中使用该so库,在这里我将之前native-lib的名字改成了hello。因此生成so库的时候也会生成libhello.so文件(生成so库的时候会自动加上lib的前缀)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T28Ek8Pz-1589177307908)(/Users/jaceyuan/md/博客/Android:NDK开发/11.png)]

    5. 在MainActivity中使用调用JniUtil中的native方法

      public class MainActivity extends AppCompatActivity {
      
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              TextView tv = findViewById(R.id.sample_text);
              //直接调用native方法
              tv.setText(JniUtil.helloFromJNI());
          }
      }
      
    6. 运行项目

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XAPnknng-1589177307909)(/Users/jaceyuan/md/博客/Android:NDK开发/12.png)]

    7. 查看so库。在app->intermediates->cmake中就会生成对应类型的so库,因为生成so库的时候会自动加上lib的前缀。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ni0VTIMh-1589177307909)(/Users/jaceyuan/md/博客/Android:NDK开发/13.png)]

    三、使用CMake引入第三方so库

    通常情况下,引入第三方.so库会有两种场景:

    • JNI规范的so。比如返回的是JNI直接支持的类型,比如说上述NdkDemo中的native-lib.cpp中的两个方法。
    • 只提供.so库和头文件。第三方共享.so库一般情况下只提供.so文件和头文件,就是没有将C++文件直接暴露给JAVA层,也没有编写JNI方法的C++文件,比如上述的hello.cpp,这个C++文件中的方法并不是JNI直接支持的类型。

    在实际开发中,更常见的是第二种场景。两种场景的引入方法不同,第一种可以直接引入第三方so库,而第二种需要引入自己的so库,然后将自己的so库与第三方so库和头文件进行相关联。接下来我们就来分析这两种引入方式。

    1. 引入JNI规范的so

    1. 新建一个普通的Android项目。引入JNI规范的.so库并不需要Native C++类型的项目。

    2. 在main中新建一个jniLibs。我们在app->src->main中新建立一个jniLibs,然后将上面生成的libhello.so文件拷贝过来,这里我们直接将上面cmake->debug->obj中的四个文件夹都拷贝过来

      在这里插入图片描述

    3. 新建一个JniUtil类。注意包名和类名都要跟引入so库中的暴露的JNI方法中的一致,接着就是加载hello这个so库,然后声明native方法,这个native方法就是hello.so库中暴露的JNI方法。其实你会发现这个JniUtil中的代码跟上述的NdkDemo中的是一样的。

      在这里插入图片描述

    4. 在MainActivity中使用JniUtil中的native方法

      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              TextView tv = findViewById(R.id.tv);
              //调用native方法
              tv.setText(JniUtil.helloFromJNI() + " & "+JniUtil.stringFromJNI());
          }
      }
      
    5. 运行项目

      在这里插入图片描述

    可以发现引入JNI规范的.so库是很简单的,因为我们知道hello.so库中接口方法的命名方式,不用CMake,不用编写C++文件,直接在JniUtil中声明native方法,然后运行即可。

    2. 引入第三方so库和头文件

    这里我们使用场景就是,在Java层中要调用hello.so库中hello.cpp中的helloWorld方法

    1. 新建一个Native C++工程

      因为引入这种类型的so库,我们需要创建自己的so文件,然后在自己的so文件里再调用第三方so,最后在Java层中调用自己的so,因此需要进行NDK开发。而上面我们已经分析了新建Native C++工程AS帮我们建立和修改的文件,因此如果你想在原有的项目中进行NDK开发的话,其实就是自己手动增加和修改这些文件即可。

    2. 新增文件夹,用来存放要导入的第三方so库以及头文件。主要是在cpp文件中新建include文件和在main中新建jniLibs,然后将第三方的头文件放在include中,第三方so库放入jniLibs中。

      在这里插入图片描述

    3. 配置CMakeLists.txt。我们需要关联第三方头文件到native-lib,并配置好第三方so库以及头文件导入的路径。这里需要注意的是set_target_properties这里配置的so库目录,你可以利用message打印,so库的路径是否正确,CMAKE_SOURCE_DIR代表着CMakeLists.txt的路径,由于我的CMakeLists.txt在cpp中,因此需要加上/…进行回退到上一级的main目录,然后配置libhello.so的相对路径。

      cmake_minimum_required(VERSION 3.4.1)
      
      # 利用这个打印路径
      message("******************************************************************")
      message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
      message("******************************************************************")
      # 这里会把 native-lib.cpp转换成共享库,并命名为 native-lib
      add_library( # 库的名字
              native-lib
      
              # 设置成共享库
              SHARED
      
              # 库的源文件(由于native-lib.app和CMakeLists.txt同处于一个包,因此可以直接写文件名
              # 不然的话需要写成src/main/cpp/native-lib.cpp)
      
              native-lib.cpp)
      
      # 如果需要使用第三方库,可以使用 find-library来找到
      find_library( # so库的变量路径名字,在关联的时候使用
              log-lib
      
              # 你需要关联的so名字
              log)
      
      #将native-lib关联到第三方库头文件
      #由于我的inclue目录与CMakeList都在cpp目录,因此可以直接写include,否则需要写相对目录
      include_directories(include)
      #导入第三库,不同到第三方库需要分开导入,因为有4个so库需要导入,因此需要4add_library(hello SHARED IMPORTED)
      #设置导入第三库名称,目标位置
      set_target_properties(hello
              PROPERTIES IMPORTED_LOCATION
              ${CMAKE_SOURCE_DIR}/../jnilibs/${ANDROID_ABI}/libhello.so)
      
      # 通过link将源文件的库和第三方库添加进来
      target_link_libraries(
              # 源文件库的名字
              native-lib
              #第三方库的名称
              hello
      
              # 添加第三方库的变量名
              ${log-lib})
      
    4. 新建JniUtil用于加载so库和声明native方法。在引入JNI规范的so库时,我们特别强调了该类要与hello.so库中的JniUtil包名,类名要一致。而在这里并不需要,因为在这里我们并不是引入hello.so库,而是引入自己的so库(native-lib),我们只是为了方便管理,然后取JniUtil。

      在这里插入图片描述

    5. 在native-lib.cpp中引入第三方头文件(hello.h)。在这里我们引入了hello.h的头文件,然后实现了对外的JNI方法,在该方法中我们引用了第三方库中的hello.cpp中的helloWorld方法,而这也就是我们引入第三方so库和头文件的最终目的。

      注意:JNI方法的包名,类名,方法名与上面的JniUtil一致

      #include <jni.h>
      #include <string>
      #include "hello.h"
      
      
      extern "C" JNIEXPORT jstring JNICALL
      Java_com_example_jnidemo_util_JniUtil_helloFromJNI(
              JNIEnv *env,
              jobject /* this */) {
          std::string helloStr = helloWorld();
          return env->NewStringUTF(helloStr.c_str());
      }
      
      
    6. 在MainActivity中引用JniUtil中的native方法

      在这里插入图片描述

    7. 运行项目。你就能发现神奇的HelloWorld

    四、踩坑

    1. 在引入JNI规范的.so库时一定要记得包名,类名要和引入的so库中的一致,不然运行时会报No implementation found for java.lang.String com.example…之类的错误,然后闪退

    2. 在引入第三so库的时候,如果你将so库放在src/main/jniLibs时,可以不在项目的build.gradle中配置so库路径,因为AS默认加载so库的路径就是src/main/jniLibs。但是如果放在其他地方的时候,或者不取名jniLibs时,比如我们放在了src/main/jniLib,这时候就得在build.gradle中配置,如下
      在这里插入图片描述

    3. 引入第二种so库和头文件中配置CMakeList.txt的时候,我们会通过set_target_properties来设置目标so库的路径,网上大部分的教程配置的路径都是:${CMAKE_SOURCE_DIR}/jnilibs/${ANDROID_ABI}/so库完整名字.so,但其实是要看具体情况的,如果你编译或运行的时候出现了类似下面这种的错误,那么大概率是由于so库的路径配置错误导致的。

       'F:/Android/JNIDemo/app/src/main/cpp/jnilibs/armeabi-v7a/libhello.so', needed by 'F:/Android/JNIDemo/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so', missing and no known rule to make it
      

      这时候我们可以利用message来打印${CMAKE_SOURCE_DIR}/jnilibs/${ANDROID_ABI}/so库完整名字.so这个路径,然后对比一下你引入第三方so库的位置,就可以进行判断是否路径配置错误。

      在这里插入图片描述

      添加打印信息后我们进行编译,如果编译错误的话,应该能够在build中看到打印的message信息,如果看不到的话,可以查看app->.cxx->cmake->debug->随便一个机型->build_output.txt中的打印信息,然后对比你引入第三方库的位置。因为我的jnilibs是放在main层,所以这个路径明显是错误的,因此需要在${CMAKE_SOURCE_DIR}加上/…进行回退一个目录,即最终的目录应该为${CMAKE_SOURCE_DIR}/../jnilibs/${ANDROID_ABI}/libhello.so

      在这里插入图片描述
      在这里插入图片描述

      如果你确定你的路径配置没有错误,那么你也可以看看报错的so库位置,也有可能是因为你运行的环境缺少了相关的机型,比如在虚拟机中运行需要x86的环境,而你引入so库的时候没有将x86的so库导进来,或者是说你的手机运行需要arm64-v8a或armeabi-v7a的环境,但是你没有引入对应的环境,也有可能missing and no known rule to make it的错误,所以最好是将所有机型的so库文件拷贝过来。

    总结

    自己是NDK开发和C++的小白,所以整个过程下来感觉收获很多。从安装到使用NDK开发,一路上下来也踩了不少的坑,所以想记录这整个过程,如果有错误的还请大家多多包涵,同时也欢迎大家指出错误。

    参考博客:

    展开全文
  • 解压密码123654,android studio 3.0.1 NDK开发完整流程,从0开始教你一步步完成开发,有完整DEMO,亲自测试,直接生成SO文件到指定路径,APP可直接使用。详细步骤参见:...
  • Android NDK开发入门

    2021-01-21 20:15:49
    神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育...
  • JNI NDK 开发指南

    2018-02-27 10:15:05
    JNI NDK 开发指南 对JNI有个初步的认识,里面涉及必要的函数说明
  • NDK开发so层与java代码相互调用
  • NDK开发的可以称之为底层开发或者jni(java native interface)层开发,SDK开发可以称为上层开发。 Android开发中使用NDK的原因: 1、众所周知,利用SDK编写的代码,生成的APK,很容易就可以反编译了,安全
  • log4cpp Android NDK 开发

    2016-03-09 19:19:16
    Android NDK开发 log4cpp
  • 什么是NDK开发(一)

    千次阅读 2019-07-06 15:58:00
    作者:代码大婶 在Android的官方文档上是这么解释NDK的:“原生开发套件 (NDK) 是一套工具,使您能够在 ...”NDK是一个Android官方提供的一个开发套件与Android SDK相对应,NDK是原生开发套件,而SDK是JAVA开发套...

    作者:代码大婶

    在Android的官方文档上是这么解释NDK的:“原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 Activity 和访问物理设备组件,例如传感器和轻触输入。”NDK是一个Android官方提供的一个开发套件与Android SDK相对应,NDK是原生开发套件,而SDK是JAVA开发套件。NDK使得Android应用可以利用C++语言高效的计算性能,进一步提升App的性能,降低延迟。说道这里,大家肯定要问NDK有那些应用场景,我为什么要用NDK呢,用JAV不行吗?下面列举一些NDK 的应用场景:

    • 重用一些现成的库,例如已经用C/C++编写好的openCV库
    • 前面提到的高性能计算,例如很多Bitmap的处理到放在NDK进行处理。
    • 一些敏感信息的保护,例如密钥等信息(密钥等信息还是要经过加盐才能放到NDK中,不然还是
      会有别反编译的风险)

    知道了应用场景,大家肯定已经摩拳擦掌准备试一试了,先bie别着急。欲善其事,先利其器。以下给出了开发NDK的三大利器。
    下面罗列NDK的三大开发组件:

    • NDK  Android原生开发套件
    • CMAKE 外部编译工具
    • LLDB  原生代码调试工具

    看到了上面的三大利器,我们要从哪儿获取呢,其实下载一个Android Studio就够了。当然下载完了要进行配置。说到配置,也很简单,就是:
    此处输入图片的描述

    JNI的前世今生

    说到NDK,如果不说JNI那不就是耍流氓嘛。为啥耍流氓??因为NDK是开发套件,JNI才是调用的框架。所以与其说是NDK开发,不如说是JNI的开发。不过NDK是Android提供的开发套件。JNI可不是,JNI全称Java Native Interface,已经不是新鲜的事情了。他可是有专门的网站来介绍它的,是不是觉得很牛逼。
    下面来简单的介绍一个JNI,JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++写的大量代码JNI是一种在Java虚拟机机制下的执行代码的标准机制。代码被编写成汇编程序或者C/C++程序,并组装为动态库。也就允许非静态绑定用法。这提供了一个在Java平台上调用C/C++的一种途径,反之亦然。
    是不是有点懵,那我就来总结一下,JAVA通过jni调用C++代码,C++代码通过jni也可以调用java代码,看下图:
    此处输入图片的描述

    一个NDK的小DEMO

    俗话说的好,“纸上得来终觉浅,觉知此事要躬行”。那我们就看一个简单的列子吧。

        public class MainActivity extends AppCompatActivity {
    
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // Example of a call to a native method
            TextView tv = findViewById(R.id.sample_text);
            tv.setText(stringFromJNI());
        }
    
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String stringFromJNI();
    }
    
    #include <jni.h>
    #include <string>
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_nanguiyu_testcpp_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    

      以上就是官方给出的Demo,我在这里稍微介绍一下,上面给出的是java代码调用C++代码,把“Hello from C++”
    这个字符串赋值给TextView,从而完成了java代码对C++的调用。顺便说一下,上面的这种注册方式属于静态注册,下面会对jni的两种注册方式进行介绍。

    JNI的两种注册方式

      jni有两种注册方式。分别是静态注册,和动态注册。为啥需要注册,注册就是让jni知道你这个函数的存在呗。下面分别从两种注册方式的优点和缺点,怎么进行注册来介绍两种注册方式。

    jni静态注册方式

    • 优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
    • 缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
    package com.nanguiyu.jnitest;
    
    public class JniTest {
        static {
            System.loadLibrary("JniTest-lib");
        }
    
        public native String stringFromJNI();
    }
    
    

    上面的Java代码首先加载静态库,然后定义native方法,注意这个方法没有实现,且前面加上标志native.

    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_nanguiyu_jnitest_JniTest_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    

    上面是C++代码,他是上面stringFromJNI方法的C++实现,功能非常简单,我们只需要关注C++中函数的名称,它的名称由com.nanguiyu.jnitest+类名+方法名,然后用下划线隔开。差点忘了,还有一个重要的东西CMakelist.txt

    # 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.4.1)
    
    # 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.
            JniTest-lib
    
            # Sets the library as a shared library.
            SHARED
    
            # Provides a relative path to your source file(s).
            JniTest.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.
            JniTest-lib
    
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib})
    

    这个文件当中add_library(JniTest-lib SHARED JniTest.cpp),第一个参数是库的名称,这个名称由我们自己来取,第二个是模式,第三个是你要编译的C++代码,新添加的C++文件,需要在这里添加。

    jni动态注册方式

    • 优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
    • 缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

    废话不多说,直接上代码。

    package com.nanguiyu.dymanicjni;
    
    public class Test {
    
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        public native String getStr();
    }
    
    

    C++部分代码

    #include <jni.h>
    #include <string>
    
    jstring getStr(JNIEnv *env){
        std::string str = "Hello World";
        return env->NewStringUTF(str.c_str());
    }
    
    static JNINativeMethod getMethods[] = {
            {"getStr","()Ljava/lang/String;",(void*)getStr},
    };
    
    static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,
            int methodNum)
    {
        jclass clazz;
        clazz = env->FindClass(className);
        if(clazz== NULL)
        {
            return JNI_FALSE;
        }
    
        if(env->RegisterNatives(clazz,getMethods,methodNum)<0)
        {
            return JNI_FALSE;
        }
    
        return JNI_TRUE;
    
    }
    
    static int registerNatives(JNIEnv* env)
    {
        const char* className = "com/nanguiyu/dymanicjni/Test";
        return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
    }
    
    JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
        JNIEnv* env = NULL;
        if(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK)
        {
            return -1;
        }
    
        assert(env!=NULL);
    
        if(!registerNatives(env)){
            return -1;
        }
    
        return JNI_VERSION_1_6;
    }
    

    动态注册相较于静态注册,是比较麻烦的,但是它有个好处具体调用的函数名称有我们自己定义,而不用按照静态注册那样把包名等等七七八八的东西全部要写上。不过有个麻烦的东西要写java方法的签名,各个签名的基本变量如下表

    签名符号C/C++java
    Vvoidvoid
    Zjbooleanboolean
    Ijintint
    Jjlonglong
    Djdoubledouble
    Fjfloatfloat
    Bjbytebyte
    Cjcharchar
    Sjshortshort
    [ZjbooleanArrayboolean[]
    [IjintArrayint[]
    [JjlongArraylong[]
    [DjdoubleArraydouble[]
    [FjfloatArrayfloat[]
    [BjbyteArraybyte[]
    [CjcharArraychar[]
    [SjshortArrayshort[]
    L完整包名加类名jobjectclass

    各位看到这些,肯定觉得麻烦的很,难道要本大婶记住这些。这是不可能地。所以我有神器啊。这里给大家介绍一个Android Studio插件 bytecodeOutline。不知道怎么下载,看这么这张图。

    下载插件








    下载插件完了之后,直接选中你要查看的文件,右键–>Show bytecode outLine,享受插件给我们带来的便利吧。
    顺便说一下,这个插件我最开始是用来写ASM字节码插桩的时候用的,发现在这儿也很好用。




    选择

    Java,C++相互调用

    下面给出简单的例子来说明怎么实现代码之间的相互调用。

    JAVA调用C++函数。

    这个不要说了,上面写的两个例子都是写的这个,参考上面。

    C++函数调用JAVA代码

    初学者可能都是用java调用C++代码。还有C++代码调用java代码这种骚操作吗?当然有,大婶亲自撸代码给出具体的例子。各位看官们不要眨眼。

    #include <jni.h>
    #include <string>
    #include <iostream>
    #include "jnilog.h"
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_nanguiyu_testcpp_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_nanguiyu_testcpp_MainActivity_test(JNIEnv *env, jobject instance, jstring val_,
                                                jint intVal) {
        LOGI("TAG","---Java_com_nanguiyu_testcpp_MainActivity_test---");
        const char *val = env->GetStringUTFChars(val_, 0);
    
        std::cout<<*val<<std::endl;
        std::cout<<intVal<<std::endl;
    
    
        LOGI("TAG","%s",val);
        LOGI("TAG","%d",intVal);
    
        std::string hello = "wo shi zhongguoren";
        jstring from = env->NewStringUTF(hello.c_str());
    
        jclass mainActivityInterface = env->FindClass("com/nanguiyu/testcpp/MainActivity");
    
        jfieldID testField = env->GetFieldID(mainActivityInterface,"intVal","I");
    
        env->SetIntField(instance,testField,1);
    
        jmethodID testMethodId = env->GetMethodID(mainActivityInterface,"testToast","()V");
    
        env->CallVoidMethod(instance,testMethodId);
    
        env->ReleaseStringUTFChars(val_, val);
        env->DeleteLocalRef(mainActivityInterface);
    }
    

    java代码

    package com.nanguiyu.testcpp;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        public String val;
    
        private int intVal;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // Example of a call to a native method
            TextView tv = findViewById(R.id.sample_text);
            tv.setText(stringFromJNI());
            findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    val = "zhangfeng";
                    test("nihao",100);
                }
            });
        }
    
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String stringFromJNI();
    
    
        public native void test(String val,int intVal);
    
    
        public void testToast(){
            Toast.makeText(this,val,Toast.LENGTH_LONG).show();
            Log.d("TAG",intVal+"");
        }
    }
    
    

    代码运行之后成功的调起了toast。仔细查看代码,是不是像反射。还真是有点像啊。

    总结

    本文简单介绍了jni最基本的用法,从开发工具,到注册方式,再从注册方式到例子说明。但是更重要的JNIEnv,和本地代码内存是怎么占用的我并没有讲。留在下一遍文章中来解说吧。对了,如果需要完整的工程代码的。评论区留言或者私信给我。

    NDK系列
    什么是NDK开发(二)

    展开全文
  • ndk开发指南

    2015-11-23 19:28:41
    ndk ,jni ,安卓,android
  • 超详细的Android NDK开发环境搭建

    千次阅读 2020-06-01 18:23:34
    技术的更新换代真的很快,以前做NDK开发用的是ndk-build,最近要用到ndk,查了一下资料,几年前已经改用CMake了,其实之前有学习过这个,但是时间一长,又给忘了,所以,好记性不如烂笔头,这次得做个笔记了。...

    前言

    技术的更新换代真的很快,以前做NDK开发用的是ndk-build,最近要用到ndk,查了一下资料,几年前已经改用CMake了,其实之前有学习过这个,但是时间一长,又给忘了,所以,好记性不如烂笔头,这次得做个笔记了。

    NDK开发环境搭建

    1. 创建一个新项目,起名为“NdkDemo”

    2. 点击AndroidStudio右上角的SDK Manager图标,安装NDK和CMake,如下
      在这里插入图片描述在这里插入图片描述

    3. 打开项目结构(Ctrl + Shift + Alt + S),并设置NDK位置,如下:
      在这里插入图片描述

    4. 创建jni目录:右击app > New > Folder > JNI Folder,此时会在app/src/main目录下生成jni目录,最新官方文档教程中使用的是cpp目录,所以也可以手动创建名为cpp的目录,创建jni也可以,在Android视图下,jni目录会显示成cpp,如下:
      在这里插入图片描述
      对于强迫症,还是想知道为什么会这样?

      在AndroidStudio3.5版本以及更新的版本中,对于native有了新的项目结构。
      旧版本native源文件存放于app/src/main/jni/目录,CMakeLists.txt存放于app/目录
      新版本native源文件和CMakeLists.txt都存放于app/src/main/cpp/目录

      所以,我们尽量使用新版本的方式吧!

    5. 创建C++文件:右击cpp > New > C/C++ Source File,在弹出的对话框中输入文件名为demo(可以随意取名),Type选择".cpp",这样就创建出了一个demo.cpp的文件,用于写C++代码。

    6. 创建CMakeLists.txt文件:右击cpp > New > File,在弹出来的输入框中输入:CMakeLists.txt (注:必须用这个名字),并输入如下代码:

      # 设置构建native library所需的CMake最低版本。
      cmake_minimum_required(VERSION 3.4.1)
      
      #创建一个库(多次调用add_library即可创建多个库)
      add_library( # 设置库的名称
                   demo-lib
      
                   # 将库设置为共享库(即so文件)
                   SHARED
      
                   # 指定源文件的相对路径
                   demo.cpp )
      

      CMakeLists.txt就是一个配置文件,指定了要编译的源文件,而且配置了要生成一个名为demo-lib的库,它的文件名为libdemo-lib.so。

    7. AndroidStudio是中构建apk文件是使用gradle来完成的,所以我们还要告诉gradle在构建的时候要编译so文件并打包到apk中,只需要把CMakeLists.txt配置文件告诉gradle即可,方法如下:
      右击app > Link C++ Project with Gradle,在弹出来的对话框中指定CMakeLists.txt文件的位置,如下:
      在这里插入图片描述
      此时会在app目录下的build.gradle文件中生成如下内容:

      android {
          externalNativeBuild {
              cmake {
                  path file('src/main/jni/CMakeLists.txt')
              }
          }
      }
      

      如果这个时候在Build窗口中报出一个构建失败的错误,如下:
      在这里插入图片描述
      大概意思是说执行native build失败了,没关系,因为我们的demo.cpp文件中什么也没写。

    8. 在MainActivity中声明一个native方法,如下:

      class MainActivity : AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
          }
       
          external fun getString(): String
      }
      

      注:这里使用的是kotlin语言,方法使用external修饰,如果是java则用native修饰。

    9. 在demo.cpp文件中实现对应的jni函数(getString方法),如下:

      #include <jni.h>
      #include <string>
      
      extern "C"
      JNIEXPORT jstring JNICALL
      Java_com_even_app_ndkdemo_MainActivity_getString(JNIEnv *env, jobject thiz) {
          std::string hello = "Hello from C++";
          return env->NewStringUTF(hello.c_str());
      }
      

      此函数的功能为返回一个字符串:“Hello from C++”。此时如果发现代码有红色报错,没关系,点击右上角的同步Gradle按钮即可消除,如下:
      在这里插入图片描述

    10. 加载动态连接库(即so文件),并调用native方法,如下:

      class MainActivity : AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              Toast.makeText(this, getString(), Toast.LENGTH_SHORT).show()
          }
      
          external fun getString(): String
      
          companion object {
              init {
                  System.loadLibrary("demo-lib")
              }
          }
      }
      
    11. 运行查看效果,如下:
      在这里插入图片描述
      OK,大告成功,如上图所示,拿到了从C++函数中返回的字符串:“Hello from C++”。

    其他细节

    1、自动生成native方法

    完成上面的例子后,如果还想再增加第二个native方法时,可以使用自动生成功能,如在MainActivity中添加一个add方法,如下:

    external fun add(x: Int, y: Int): Int
    

    此时会报错,把光标定位到add上,按Alt + Enter,选择创建jni函数即可,如下:
    在这里插入图片描述
    此时就会自动在demo.cpp中增加对应的jni函数,如下:

    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_even_app_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint x, jint y) {
        // TODO: implement add()
    }
    

    在创建第一个jni函数的时候无法使用此方式来自动生成,这应该算是AndroidStudio的一个Bug吧,只有手动写了一个jni函数之后,第二个才会出现创建jni函数的命令。

    2、使用创建向导生成带ndk的项目

    File > New > New Project > Native C++,此时可能会报一个错,如下:
    在这里插入图片描述
    错误说的是NDK没有配置,这应该也算AndroidStudio的一个Bug吧,我已经安装有NDK,有好几个版本(包含最新版本),AndroidStudio应该自动给我选一个,用最新的也行。上面描述说的意思是首选的NDK版本是21.0.6113669,我并没有安装这个版本,所以它提示我们安装,其实没有必要安装这个版本的,按Ctrl + Shift + Alt + S打开项目结构,在弹出来的对话框中设置一个已经安装的NDK位置即可。然后就可以直接运行查看效果了。

    如果是要做一个新项目,这样的方式创建NDK项目非常方便。

    使用向导创建的NDK项目还会在app下的build.gradle中多一个cppFlag的配置,如下:

    android {
    
        defaultConfig {
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
        }
    	
    }
    

    这个应该是用于指定C++的版本,cppFlags为空,这是因为我在创建这个项目的向导中选择的C++版本时选择了默认,如下:
    在这里插入图片描述
    点击下拉箭头,可以看到我们当前设置的NDK的版本所支持的C++版本,不同的NDK版本可能会支持不同的C++版本,截图如下:
    在这里插入图片描述
    假如我们选择C++17,则cppFlag属性如下:

    android {
    
        defaultConfig {
            externalNativeBuild {
                cmake {
                    cppFlags "-std=c++17"
                }
            }
        }
    	
    }
    

    3、查看生成的so文件

    a、通过生成的apk中提取

    直接在AndroidStudio中运行项目时,AndroidStudio会根据手机的CPU类型来生成对应的CPU架构的so文件。点击Build > Analyze APK,在弹出来的对话框中选择app\build\outputs\apk\debug\app-debug.apk,这样就能看到apk文件的内部结构了,如下:
    在这里插入图片描述
    因为我的手机CPU是arm64-v8a结构的,所以直接运行时它只生成和手机对应的so文件,如果想要生成所有的,可以点击Build > Build Bundle(s) / APK(s) > Build APK(s),这样的话也会生成debug.apk,但是这个apk并不针对哪一个手机,所以会生成所有的so,如下:
    在这里插入图片描述
    这里说的所有的so是指特定NDK版本支持的so CPU类型,从NDK 17开始,不再支持armeabi,所以上图中没有看到armeabi的so。为什么不支持armeabi了呢?因为现在的手机基本上都支持armeabi-v7a了。

    如果只想配置生成特定的so类型,可以在app下的build.gradle中设置如下:

    android {
        
        defaultConfig {
            ndk {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
                abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
            }
        }
    
    }
    

    从apk中提取so文件很容易,把apk扩展名改成rar,然后用压缩软件解压即可。

    b、通过build目录中获取

    通过生成apk,从apk中获取是比较容易记的,从build目录中就需要记一下目录结构,有时候会记不住,但是也没关系,记不住的时候就从apk中取so嘛。
    在Build Variants面板中选择release版本,默认是debug版本,不知道有什么区别,但是选release肯定是比较正规嘛,准没错。然后执行菜单命令:Build/Build Bundle(s)/Apk(s)/Build Apk(s),这样就会生成一个release版本的apk,是一个没有签名的apk(如果gradle文件中没有配置签名的话),生成apk的同时也会生成对应的so文件,在如下位置:
    在这里插入图片描述

    4、相关连接

    NDK入门:https://developer.android.google.cn/ndk/guides
    代码实验室:https://codelabs.developers.google.com/codelabs/android-studio-cmake/#0
    Github上提供的NDK Demo:https://github.com/android/ndk-samples
    关于CMake:https://developer.android.com/ndk/guides/cmake.html
    配置CMake:https://developer.android.google.cn/studio/projects/configure-cmake#add-ndk-api
    添加native代码:https://developer.android.google.cn/studio/projects/add-native-code
    JNI相关:https://developer.android.google.cn/training/articles/perf-jni
    JNI接口规范:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

    5、关于生成so文件到jniLibs目录

    jniLibs目录在main目录下,如:
    在这里插入图片描述

    以前我们都是把so文件放到jniLibs目录中来使用,现在好了,打包时直接编译源码为so并打包到apk。人有时候就是想使用原来的方式,网上找了一下,在CMakeLists.txt中加入下面这句代码:

    # 设置so文件输出到jniLibs目录中
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
    

    在AndroidStudio中直接运行应用,确实会在jniLibs目录中生成对应的so文件了,但是报一个错误,如下:

    More than one file was found with OS independent path ‘lib/armeabi-v7a/libdemo-lib.so’. If you are using jniLibs and CMake IMPORTED targets, see https://developer.android.com/studio/preview/features#automatic_packaging_of_prebuilt_dependencies_used_by_cmake

    提示中有一个链接,但是点击这个连接之后找不到相关的内容,应该是Google重新编辑网页,删除掉了,这个链接是跳转到AndroidStudio4.1的新特性的,在这里面会有“Automatic packaging of prebuilt dependencies used by CMake”的相关内容,但是已经被编辑掉了,在Google中搜索这段英文时能找到链接,但是要点快照,出来的网页就会有需要的内容,这里把原文搬过来,并配上翻译如下:
    Automatic packaging of prebuilt dependencies used by CMake
    自动打包CMake使用的预构建依赖项
    Prior versions of the Android Gradle Plugin required that you explicitly package any prebuilt libraries used by your CMake external native build by using jniLibs:
    早期版本的Android Gradle插件要求您使用以下命令显式打包CMake外部本机内部版本使用的所有预构建库 jniLibs:

    sourceSets {
        main {
            // The libs directory contains prebuilt libraries that are used by the
            // app's library defined in CMakeLists.txt via an IMPORTED target.
            // libs目录包含预构建的库,这些库由CMakeLists.txt中通过导入目标定义的应用程序库使用。
            jniLibs.srcDirs = ['libs']
        }
    }
    

    With Android Gradle Plugin 4.0, the above configuration is no longer necessary and will result in a build failure:
    使用Android Gradle Plugin 4.0时,不再需要上述配置,并且会导致构建失败:

    What went wrong:
    Execution failed for task ‘:app:mergeDebugNativeLibs’.
    A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
    More than one file was found with OS independent path ‘lib/x86/libprebuilt.so’

    External native build now automatically packages those libraries, so explicitly packaging the library with jniLibs results in a duplicate. To avoid the build error, simple remove the jniLibs configuration from your build.gradle file.
    现在,外部本机构建会自动打包这些库,因此将jniLibs结果与库明确打包在一起。为避免生成错误,只需jniLibs从build.gradle文件中删除配置即可。

    展开全文
  • ubuntu Android NDK开发环境的搭建
  • 本文主要介绍Android NDK开发,这里详细整理了相关资料并介绍 NDK的知识和开发流程及简单示例代码,帮助大家学习,有需要的小伙伴可以参考下
  • NULL 博文链接:https://android-zhang.iteye.com/blog/1779317
  • AndroidStudio中 NDK开发

    2016-12-22 15:58:50
    AndroidStudio中 NDK开发,动态so库
  • android ndk 开发入门小例子。 博客地址http://blog.csdn.net/u014702653/article/details/51861013
  • 所以觉得自己来一篇,本文将详细介绍关于android搭建ndk开发环境及第一个jni调用程序的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。 一:ndk环境搭建 1:开发环境 我使用的是...
  • 在之前的博客中已经为大家介绍了,如何在win环境下配置DNK程序,本篇我将带大家实现一个简单的Hello jni程序,让大家真正感受一下NDK开发的魅力。这里我们选择使用C+JAVA开发Android程序,首先你必须了解C语言、JAVA...
  • JNI—NDK开发流程(ndk-build与CMake)

    千次阅读 2019-01-16 13:52:23
    注意,例子使用的各版本信息如下: AS 3.3 Gradle Tool:3.3.0 Gradle Version:4.10.1 1. 概述 最近在阅读Android 源码的过程中发现大量的Native方法,在...NDK开发流程? C/C++ 与 Java如何相关通信的? 如何阅...

    注意,例子使用的各版本信息如下:
    AS 3.3
    Gradle Tool:3.3.0
    Gradle Version:4.10.1

    1. 概述

    最近在阅读Android 源码的过程中发现大量的Native方法,在没有系统掌握JNI与NDK知识的情况下寸步难行,所以有必要系统地了解相关知识。
    在学习之前,我常常有有如下几个疑问:

    NDK的开发流程?
    C/C++ 与 Java如何进行通信的?
    如何阅读Android Native 源码?

    希望经过一系列的学习和总结,以上的问题能得到解决。 今天首先解决“如何进行NDK开发?”

    1.1. JNI:

    • JNI :
      • Java Native Interface,Java本地编程接口,是一套编程规范,提供了本地语言(C/C++)与Java 语言相互通信的一套API接口;
      • Java是跨平台语言,因为其背后依赖Java虚拟机,Java源码编译的.class字节码文件运行在虚拟机上,而虚拟机往往由C/C++编写,那么两者是如何通信的呢?——JNI;
      • 通过JNI, Java可以调用C/C++的代码,反过来亦然;
      • JNI 是Java 语言的一部分,而非Android 平台特有;
    • 作用:
      • 提高效率:加密算法、图像处理、机器视觉等需要大量计算的逻辑,可以在C/C++中实现;
      • 扩展JVM:扩展JVM的功能;
      • 代码复用:复用C/C++平台上的代码,避免重复在Java上重复造轮子;

    1.2. NDK:

    • NDK:

      • Native Develop Kit,本地开发工具,是Google开发的一套方便开发者在Android 平台上开发Native 代码的工具;
      • NDK 是Android 的一部分,与Java无直接关系;
    • 作用:

      • 方便快速:使用NDK自带的工具,快速对C/C++代码进行构建、编译和打包;
      • 交叉编译:快速生成不同CPU平台的动态库;

    2. NDK开发流程

    在NDK的开发过程中,有两种形式——ndk-build和CMake,其实两种方式最终的目的都是一样,将C 或 C++(“原生代码”)嵌入到 Android 应用。只是CMake使用起来更加方便,同时Android官方也推荐在Android Studio 2.2及以上使用CMake。工具的本质就是方便人们进行日常活动,对比一下两者流程,方便开发过程进行技术选型。

    2.1. ndk-build

    • 下载和配置NDK
      ndk-build只是NDK工具包里面的一个脚本工具,帮忙调用NDK里面正确的构建脚本,路径在sdk/ndk-bundle目录下。
      需要在Studio中下载与配置NDK:Settings–Appearance & Behavior–System Settings–Android SDK–SDK Tools。
      image

    • Java中创建本地方法
      在Java类HelloWorldJNI中创建本地方法:

      public class HelloWorldJNI {
      
          // 加载Native动态库(so库),动态库的名称后面在mk文件中会使用到
          static {
              System.loadLibrary("helloworld_jni");
          }
      
          // 定义Native方法
          public native String getString();
      
          public native String setString(String text);
      }
      
    • 创建JNI文件目录并链接项目
      在src\main 目录下创建 jni文件夹,JNI相关的编码都在讲在该文件下进行,有两种创建方式:
      a. Android Studio UI下右击main文件下创建:
      image
      b. 手动创建jni文件:
      在main文件夹手动创建jni文件夹,然后在项目的gradle中配置

      android {
          ......
          externalNativeBuild {
              ndkBuild {
                  path 'src/main/jni/Android.mk'
              }
          }
      }
      

      或者
      image
      两者效果一样,推荐使用方法a,简单快捷。

    • 生成本地方法头文件
      在命令终端Terminal中进入到项目的java文件目录中,如本例中的 DemoProjects\JNITest\src\main\java,执行命令:

      javah -jni -encoding UTF-8 -d <你的工程目录>\DemoProjects\JNITest\src\main\jni com.wuzl.jnitest.HelloWorldJNI
      

      -jni:表示生成jni格式的头文件;
      -encoding:编码格式;
      -d:头文件的输出路径;

      javah 命令导出HelloWorldJNI的头文件(com_wuzl_jnitest_HelloWorldJNI.h)至jni文件目录中,重点来了!
      头文件的命名规则为:
      包名(.变成下划线)
      其函数命名遵循如下规则:
      Java_包名_类名_方法名

          /* DO NOT EDIT THIS FILE - it is machine generated */
          #include <jni.h>
          /* Header for class com_wuzl_jnitest_HelloWorldJNI */
          
          #ifndef _Included_com_wuzl_jnitest_HelloWorldJNI
          #define _Included_com_wuzl_jnitest_HelloWorldJNI
          #ifdef __cplusplus
          extern "C" {
          #endif
          /*
           * Class:     com_wuzl_jnitest_HelloWorldJNI
           * Method:    getString
           * Signature: ()Ljava/lang/String;
           */
          JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_getString
            (JNIEnv *, jobject);
          
          /*
           * Class:     com_wuzl_jnitest_HelloWorldJNI
           * Method:    setString
           * Signature: (Ljava/lang/String;)Ljava/lang/String;
           */
          JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_setString
            (JNIEnv *, jobject, jstring);
          
          #ifdef __cplusplus
          }
          #endif
          #endif
      
    • 创建C/C++ 代码文件
      在jni目录下创建helloworld.cpp,然后导入上面生成的头文件,并实现头文件声明的两个本地方法

      # include <jni.h>
      # include <stdio.h>
      #include <cstring>
      # include "com_wuzl_jnitest_HelloWorldJNI.h"
      
      # ifdef _cplusplus
      extern "C"
      {
      # endif
      
          JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_getString(JNIEnv *env, jobject obj){
              return env -> NewStringUTF("Get Hello world JNI!");
          }
      
          JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_setString(JNIEnv *env, jobject obj, jstring str){
              char* jnistr = (char *) env->GetStringUTFChars(str, NULL);
              strcat(jnistr,": I am JNI");
              return env -> NewStringUTF(jnistr);
          }
      # ifdef _cplusplus
      }
      # endif
      
    • 创建配置文件
      在jni文件目录下配置Android.mk和Application.mk配置文件
      Android.mk

      # 当前模块路径,即Android.mk所在的文件目录
      LOCAL_PATH       :=  $(call my-dir)
      
      # 开始清除LOCAL_XXX变量
      include              $(CLEAR_VARS)
      
      # 动态库模块名称,Java代码中中加载动态库中使用System.loadLibrary("helloworld_jni");
      LOCAL_MODULE     :=  helloworld_jni
      
      # 编译的C/C++源文件,可多个,以空格隔开
      LOCAL_SRC_FILES  :=  helloworld.cpp
      
      # 开始动态编译生成动态库
      include $(BUILD_SHARED_LIBRARY)
      

      Application.mk

      # 需要编译的ABI
      APP_ABI :=armeabi-v7a x86 x86_64
      

      配置文件的详细选项可查看Google官网:
      Android.mk, Application.mk

    • 直接运行
      Alt

    • 可利用ndk-build命令工具生成so库
      在命令终端Terminal中进入到jni目录的父目录,直接执行ndk-build命令(需要将ndk目录添加到环境变量中)将会在当前目录下生成libs和obj文件目录,其中libs就是我们想要so库
      image


    小技巧:
    ndk-build NDK开发流程讲得差不多了,但是有个小技巧可以分享给大家,在上述流程中,每次都需要输入命令来生成头文件和so库,而且有时候忘记命令了,其实AS提供了相关技巧来优化开发流程:
    Settings–External Tools–“+”
    image
    其中 Program:javah所在的路径;
    Arguments: 命令参数,此处为-jni -encoding UTF-8 -d M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\src\main\jni F i l e C l a s s FileClass FileClass
    Wroking directory: 工作目录,此处为 M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\src\main\java;
    使用(右击定义Native方法的Java文件,选择External Tools-javah-jni,然后在jni文件下直接生成对应的头文件):
    image

    2.2. CMake

    CMake并不是什么新鲜的内容,脱离Android来说,CMake是一个跨平台的安装编译工具,通过简单的脚本语句描述不同平台的编译过程,已经运用在UNIX和WindowW系统中。

    Google在AS2.2版本上开始支持CMake,并提倡在AS 2.2及以上版本使用CMake进行JNI开发,在上述使用ndk-build开发过程中发现,需要的步骤特别多,比如生成头文件、配置Android.mk, Application.mk文件,并需要了解其中mk各种繁琐的配置项,而使用CMake进行开发可以省略这些步骤。

    官网已经给出非常详细的使用过程:向您的项目添加 C 和 C++ 代码

    如果是新建项目使用CMake进行JNI开发,非常简单,在创建新项目的时向导面板中选中 Include C++ Support 复选框即可,AS会创建一个简单的DEMO供使用参考。
    但是我们大多数都是需要在已存在的项目中进行JNI的开发,下面将以此作为讲解。

    • 创建CMakeLists.txt配置文件
      在项目根目录下创建CMakeLists.txt配置文件

      # CMake 最小工具版本要求,可通过SDK Manager 查看CMake的本地版本
      # 该版本对Gradle有要求,比如3.10 版本需要Gragle 4.10+version才行
      cmake_minimum_required(VERSION 3.6)
      
      add_library( # Sets the name of the library.
                   # 动态库的名称,与ndk-build中Android.mk 的模块名一样
                   # 最终编译出来的动态库名称为:lib+library name
                   helloworld
      
                   # Sets the library as a shared library.
                   # 编译库的类型,有静态库、动态库、和模块库
                   SHARED
      
                   # Provides a relative path to your source file(s).
                   # C/C++ 源文件
                   src/main/cpp/helloworld.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.
      # 找到依赖库,比如下面将系统log-lib链接到helloworld动态库
      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.
              helloworld
      
              # Links the target library to the log library
              # included in the NDK.
              ${log-lib} )
      

      这个是最最基本的CMake的配置,CMake还可以做很多强大的配置选项,详情可查看CMake的官方文档(https://cmake.org/documentation/)

    • 创建C/C++源文件
      在main目录(当然目录可以自己选择)下创建cpp文件夹,然后在该文件夹下创建helloworld.cpp源文件,与ndk-build一致:

      # include <jni.h>
      # include <stdio.h>
      # include <cstring>
      
      extern "C" {
          JNIEXPORT jstring JNICALL Java_com_wuzl_cmake_HelloWorldJNI_getString(JNIEnv *env, jobject obj){
              return env -> NewStringUTF("Get Hello world JNI");
          }
      
          JNIEXPORT jstring JNICALL Java_com_wuzl_cmake_HelloWorldJNI_setString(JNIEnv *env, jobject obj, jstring str){
              char* jnistr = (char *) env->GetStringUTFChars(str, NULL);
              strcat(jnistr,": I am JNI by CMake building");
              return env -> NewStringUTF(jnistr);
          }
      }
      
    • 链接项目
      右击项目,选择Lick C++ Project with Gradle,然后在弹出来的选项里选择CMake并选择上面配置好的CMakeLists.txt
      image

    • 运行查看结果
      image

    3. 总结

    NDK开发的两种流程:

    1. ndk-build+Android.mk+Application.mk
    2. CMake+CMakeLists.txt

    两者只是构建的脚本和命令不同而已,但是个人感觉CMake更加方便和简洁
    Google推荐使用CMake进行JNI的开发,而且CMake的配置更加强大,其官方文档可查阅使用的细节(https://cmake.org/documentation/)。

    最后上DEMO:https://github.com/PlepleLiang/JNIDemo


    参考:
    https://blog.csdn.net/quwei3930921/article/details/78820991
    https://juejin.im/post/5a67dcdb518825732c53b338
    https://developer.android.com/studio/projects/add-native-code?hl=zh-cn

    展开全文
  • 本篇文章是对Android中的配置环境进行了详细的分析介绍,需要的朋友参考下
  • ndk开发 由于需调用底层代码申请系统内存就开发了调用底层的so 申请内存主要两个 申请内存 释放内存 创建对应的java调取接口jni //com.aa.factorylibrary.jni.MemOpJni public class MemOpJni { static { System....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,671
精华内容 22,268
关键字:

ndk开发