如何在Android Studio下进行NDK开发

2022-08-10,,,,

    在as中进行ndk开发之前,我们先来简单的介绍几个大家都容易搞懵的概念:

        1. 到底什么是jni,什么是ndk?

        2. 何为“交叉编译”?

    先看什么是jni?jni的全称就是java native interface,即java本地开发接口。可能大家和我一样,一听到接口什么的就犯懵:“我也知道这是java本地开发接口的意思,但它具体是个什么意思我还是搞不明白。”其实jni它就是一种协议,一说协议,那它就是对某种东西的一个规范和约束,说的好听一点就是标准化。如果你想用我这个东西,那你必须要遵守我这边的规范。像http协议一样,http作为超文本传输协议,它规范了我们上网时从客户端到服务器端等一系列的运作流程。正因为如此,我们才能畅通无阻的上网。那么换做jni也一样,只不过jni这个协议是用来沟通java代码和外部的本地代码(c/c++)。也就是说有了jni这个协议,我们才能够随意的让java代码调用c/c++的代码,同样c/c++的代码也可以调用java的代码。如果没有这个协议作为支撑,那么java和c/c++代码想要相互调用是不可能的。下面通过两个图简单看一下jni协议在系统架构中处于什么位置:

    在上图中,上层绿色的部分一般都是用java代码写的,下层橘黄色的部分一般都是用c/c++代码写的。可以看出,正式由于有了中间jni的存在我们才可以在application层通过jni调用下层中的一些东西。了解了jni的概念后,我们再看看ndk,ndk(native development kit)就比较好理解了,它就是一个本地开发的“工具包”。java开发要用到jdk,android开发要用到sdk,那我们在android中要进行native开发,也要用到它对应的工具包,即ndk。通俗的来讲,ndk就是帮助我们可以在android应用中使用c/c++来完成特定功能的一套工具。 ndk的作用有很多,我们简单的列举两个,比如:

        1.首先ndk可以帮助开发者“快速”开发c(或c++)的动态库。

        2.其次,ndk集成了“交叉编译器”。使用ndk,我们可以将要求高性能的应用逻辑使用c开发,从而提高应用程序的执行效率。

    上面提到了“交叉编译”,我们最后再解释一下什么是交叉编译。大家都知道编译器在将中间代码连接成当前计算机可执行的二进制程序时,连接程序会根据当前计算机的cpu、操作系统的类型来转换。而根据运行的设备的不同,cpu的架构也是不同,大体有如下三种常见的cup架构:

  • arm结构 :主要在移动手持、嵌入式设备上。我们的手机几乎都是使用的这种cup架构。
  • x86结构 : 主要在台式机、笔记本上使用。如intel和amd的cpu 。
  • mips架构:多用在网关、猫、机顶盒等设备。

若想在使用了基于x86架构cpu的操作系统上编译出可以在基于arm结构cpu的操作系统上运行的代码,就必须使用交叉编译。所以综上所述:交叉编译就是在一个平台下(比如:cpu架构为x86,操作系统为windows)编译出在另一个平台上(比如:cpu架构为arm,操作系统为linux)可以执行的二进制代码。google提供的ndk就可以完成交叉编译的工作。   

好了,上面的基本概念介绍完以后,我们正式进入as下ndk开发的讲解。   

1.首先,你需要把ndk下载下来ndk下载。下载完成后解压到任意目录即可(路径中不要带有中文字符)。我的就直接放在d盘的ndk目录下:                  

   

2.在as中为你的项目配置ndk。首先新建一个android工程jnidemo,ctrl + shift + alt + s打开project structrue把我们刚才下载好的ndk配置进去,点击ok。

  

  3.配置好ndk后,简单的为我们的项目布局文件添加一个textview和一个button,当点击button的时候,我们通过调用底层自己写好的c/c++代码来返回一个字符串,最后呈现在textview上。activity_main.xml布局代码:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <textview
    android:id="@+id/textview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="hello world!"
    />
  <button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="button"/>
</linearlayout>

mainactivity.java

public class mainactivity extends appcompatactivity {

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);

    final textview textview = findviewbyid(r.id.textview);
    button button = findviewbyid(r.id.button);

    button.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        textview.settext(jniutils.sayhellofromjni());
      }
    });
  }
}  	

上面代码中的jniutils.sayhellofromejni()就是我们在与mainactivity相同的包中新建jniutils类后在里面编写的native方法。如下所示:

可以看到我们上面的sayhellofromjni()方法显示的是警告红色。把鼠标放到上面,它会提示我们对应的jni头文件没有查找到。那么接下来我们要做的就是去生成与这个sayhellofromjni()方法所对应的头文件。   

 4.生成头文件。快捷键alt + f12调出as下的terminal窗口,在terminal命令行窗口中输入如下几条指令,回车:

前面两个cd命令没什么好说的,就是先进入当前项目的app目录下,然后再进入java目录下。我们重点说一下最后一条命令:javah -d ../jni com.example.zhangxudong.jindemo.jniutils。首先,要生成java类对应的头文件我们就必须要用到javah这个命令,其次-d表示生成一个目录,那生成一个什么样的目录,具体又在哪里去生成这个目录呢?后面的../jni告示了我们。../表示在当前目录的上一层目录,我们当前在java目录下,那么它的上层目录就是main目录了。而jni就表示我们生成的目录的名称。所以整个../jni就表示在main目录下生成一个名为jni的目录。最后一个com.example.zhangxudong.jindemo.jniutils就是我们在上面新建的jniutils的完整类名了。执行完这几天指令后,刷新一下目录我们就可以在main目录下看到jni这个目录,并且在它里面生成了我们jniutils类所对应的头文件。进入头文件中,代码是如下这个样子:

/* do not edit this file - it is machine generated */
#include <jni.h>
/* header for class com_example_zhangxudong_jnidemo_jniutils */

#ifndef _included_com_example_zhangxudong_jnidemo_jniutils
#define _included_com_example_zhangxudong_jnidemo_jniutils
#ifdef __cplusplus
extern "c" {
#endif
/*
 * class:   com_example_zhangxudong_jnidemo_jniutils
 * method:  sayhellofromjni
 * signature: ()ljava/lang/string;
 */
jniexport jstring jnicall java_com_example_zhangxudong_jnidemo_jniutils_sayhellofromjni
  (jnienv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

5.头文件生成以后,我们就需要编写我们的c/c++代码了。右键jni目录---->new--->c/c++ source file

输入要新建的c/c++文件名称jnihello,这里我们用c++来编写,所以type为.cpp,如果你选择用c来编写,那么type选为.c,点击ok。这里说一下,在我们进行ndk开发的时候,选择用c还是c++,在编写代码的时候除了c和c++基本的语法不同外,还是有许多不同地方需要注意。我们后续会慢慢介绍。这里先默认跟着我的步骤来。                           

jnihello.cpp代码如下:

#include "com_example_zhangxudong_jnidemo_jniutils.h"
jniexport jstring jnicall java_com_example_zhangxudong_jnidemo_jniutils_sayhellofromjni
    (jnienv *env, jclass jclass){
return env->newstringutf("hello world from jni!!!!!");
}

可以看到我们首先需要把原来生成的jniutlis对应的头文件引入进来,下面的代码基本都是从com_example_zhangxudong_jnidemo_jniutils.h中复制粘贴过来的一部分,然后稍加修改。修改的地方主要有sayhellofromjni的两个参数和里面的简单实现,参数方面就是加了env和jclass两个字段。函数里面的实现呢,就是简单的返回一个字符串“hello world from jni!!!!!”,至于为什么这么写,我会在下一篇文章进行讲解,大家现在就需要知道如果要在这里返回一个字符串就必须要通过env->newstringutf("xxxxxx");这行代码。   

6.上面的搞定以后,我们需要在app的build.gradle中的defaultconfig中加入如下代码。它表示项目在编译时生成的动态库的名字。

最后,我们还需在jniuitls中加载我们生成的动态库:

public class jniutils {
  static {
    system.loadlibrary("jnihello");
  }
  public static native string sayhellofromjni();
}

我们把加载动态库的代码放到静态代码块中,就是表示在jniutils这个类在加载的时候就去加载我们的动态库。   

7.经过上面的5步,关于如何在as中进行简单的ndk所需要的步骤差不多就讲完了。不过还有最后一点需要注意。到这里我们基本就可以执行一下我们的项目了,现在运行一下项目试一试......不出意外的话项目是build不成功的,它会报如下的错误:

error:execution failed for task ':app:compiledebugndk'.
> error: flag android.usedeprecatedndk is no longer supported and will be removed in the next version of android studio. please switch to a supported build system.
consider using cmake or ndk-build integration. for more information, go to:
https://d.android.com/r/studio-ui/add-native-code.html#ndkcompile
to get started, you can use the sample ndk-build script the android
plugin generated for you at:
e:\jnidemo\app\build\intermediates\ndk\debug\android.mk
alternatively, you can use the experimental plugin:
https://developer.android.com/r/tools/experimental-plugin.html
to continue using the deprecated ndk compile for another 60 days, set
android.deprecatedndkcompilelease=1515317190556 in gradle.properties

因为我这里用的是android studio3.0,报出的这个错误很可能和原来版本的as不同,以前出现类似错误的时候,我们的解决方案一般都是在gradle.properties中添加一行这样的代码:android.usedeprecatedndk=true就搞定了。但是as换为3.0后你可以再试一下这种方案,肯定是不行的,它会提示你“flag android.usedeprecatedndk is no longer supported and will be removed in the next version of android studio.  please switch to a supported build system.”大体意思就是最新的as已经不支持usedeprecatedndk这个标记了,并且在后续版本的as中,它将被移除。所以我们新的解决方案就是按照它的提示在gradle.properties中添家android.deprecatedndkcompilelease=1515317190556这行代码。

最后我们运行一下项目,点击button,效果如下。可以看到,我们成功的通过java代码调用了c++的代码,并返回hello world from jni!!!!!这个字符串。          

那我们生成的动态库(.so文件)都在哪里呢?点开app--->build--->intermediates--->ndk--->debug--->libs,可以看到各个平台对应的动态库都已经生成了。                                  

到此这篇关于如何在android studio下进行ndk开发的文章就介绍到这了,更多相关android studio下ndk开发内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持! 

《如何在Android Studio下进行NDK开发.doc》

下载本文的Word格式文档,以方便收藏与打印。