天天看点

NDK 入门

NDK

概念

Native是

Native Development Kit

的简写,是Android的开发工具包,属于Android,与Java无关系。

它可以快速开发C/C++的动态库,自动将.so和应用一起打包为APK。因此我们可以通过NDK来在Android开发中通过JNI与Native方法交互。

使用方式

  1. 配置 Android NDK环境(在SDK Manager中下载NDK、CMake、LLDB)
  2. 创建 Android 项目,与 NDK进行关联(创建项目时选择C++ support)
  3. 在 Android 项目中声明所需要调用的 Native方法
  4. 用Native语言实现在Android中声明的Native方法
  5. 通过 ndk-bulid 命令编译产生.so库文件

将Android项目与NDK关联

配置NDK路径

local.properties中

加入如下一行即可

ndk.dir=<ndk路径>

添加配置

在Gradle的 

gradle.properties

中加入如下一行,目的是对旧版本的NDK支持

android.useDeprecatedNdk=true

添加ndk节点

在build.gradle中的

defaultConfig

android

中加入如下的

externalNativeBuild

节点

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.n0texpecterr0r.ndkdemo"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test<span class="emoji emoji-sizer" style="background-image:url(/emoji-data/img-apple-64/1f3c3.png)" data-codepoints="1f3c3"></span>1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
           

开发Native代码

在Java文件中声明native方法

我们首先需要在Java代码的类中通过static块来加载我们的Native库。可以通过如下代码,其中loadLibrary的参数是在CMakeList.txt中定义的Native库的名称

static {
    System.loadLibrary("native-lib");
}
           

之后,我们便可以在这个类中声明Native方法

public native String getStringFromJNI();
           

创建CMakeList.txt

我们还需要在src中创建一个CMakeList.txt文件,这个文件约束了Native语言源文件的编译规则。比如下面

cmake_minimum_required(VERSION 3.4.1)

add_library(native-lib SHARED src/main/cpp/native-lib.cpp)

find_library(log-lib log)

target_link_libraries(native-lib ${log-lib})
           

add_library

方法中定义了一个so库,它的名称是native-lib,也就是我们在Java文件中用到的字符串,而后面则跟着这个库对应的Native文件的路径

find_library

则是定义了一个路径变量,经过了这个方法,log-lib这个变量中的值就是Android中log库的路径

target_link_libraries

则是将native-lib这个库和log库连接了起来,这样我们就能在native-lib中使用log库的方法。

创建Native方法文件

在前面的CMake文件中可以看到,我们把文件放在了src/main/cpp/,因此我们创建cpp这个目录,在里面创建C++源文件native-lib.cpp。

然后, 我们便可以开始编写如下的代码:

#include <jni.h>
#include <string>

extern "C"{
  JNIEXPORT jstring JNICALL
  Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI(
      JNIEnv* env,
      jobject) {
    std::string hello = "IG牛逼";
    return env->NewStringUTF(hello.c_str());
  }
}
           

此处我们使用的是C++语言,让我们来看看具体的代码。

首先我们引入了jni需要的jni.h,这个头文件中声明了各个jni需要用到的函数。同时我们引入了C++中的string.h。

然后我们看到extern "C"。为了了解这里为什么使用了extern "C",我们首先需要知道下面的知识:

在C中,编译时的函数签名仅仅是包含了函数的名称,因此不同参数的函数都是同样的签名。这也就是为什么C不支持重载。

而C++为了支持重载,在编译的时候函数的签名除了包含函数的名称,还携带了函数的参数及返回类型等等。

试想此时我们有个C的函数库要给C++调用,会因为签名的不同而找不到对应的函数。因此,我们需要使用

extern "C"

来告诉编译器使用编译C的方式来连接。

接下来我们看看JNIEXPORT和JNICALL关键字,这两个关键字是两个宏定义,他主要的作用就是说明该函数为JNI函数。

而jstring则对应了Java中的String类,JNI中有很多类似jstring的类来对应Java中的类,下面是Java中的类与JNI类型的对照表

NDK 入门

我们继续看到函数名

Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI

。其实函数名中的_相当于Java中的 . 也就是这个函数名代表了

java.com.n0texpecterr0r.ndkdemo.MainActivity.java

中的

getStringFromJNI

方法,也就是我们之前定义的native方法。

格式大概如下:

Java_包名_类名_需要调用的方法名

其中,Java必须大写,包名里的

.

要改成

_

_

要改成

_1

接下来我们看到这个函数的两个参数:

  • JNIEnv* env:代表了JVM的环境,Native方法可以通过这个指针来调用Java代码
  • jobject obj:它就相当于定义了这个JNI方法的类 (MainActivity) 的this引用

然后可以看到后面我们创建了一个string hello,之后通过

env->NewStringUTF(hello.c_str())

方法创建了一个jstring类型的变量并返回。

在Java代码中调用native方法

接着,我们便可以在MainActivty中像调用Java方法一样调用这个native方法

TextView tv = findViewById(R.id.sample_text);
tv.setText(getStringFromJNI());
           

我们尝试运行,可以看到,我们成功用C++构建了一个字符串并返回给Java调用:

NDK 入门

CMake

我们在NDK开发中使用CMake的语法来编写简单的代码描述编译的过程,由于这篇文章是讲NDK的,所以关于CMake的语法就不再赘述了。。。

NDK 入门

想学习更多Android知识,或者获取相关资料请加入Android技术开发交流2群:862625886。 有面试资源系统整理分享,Java语言进阶和Kotlin语言与Android相关技术内核,APP开发框架知识, 360°Android App全方位性能优化。Android前沿技术,高级UI、Gradle、RxJava、小程序、Hybrid、 移动架构师专题项目实战环节、React Native、等技术教程!架构师课程、NDK模块开发、 Flutter等全方面的 Android高级实践技术讲解。还有在线答疑