本文基于 Android Studio 3.4.2 、gradle:3.2.1

1、什么是 JNI、NDK?

JNI 是 Java Native Interface (Java 本地接口)的缩写,是 Java 与其他语言通信的桥梁。在 Android 中的应用主要为:音视频开发、热修复、插件化、逆向开发和系统源码调用,为了方便使用 JNI 技术,Android 提供了 NDK 工具集合,它和 JNI 开发本质上没有区别,NDK 是在 Android 中实现 JNI 的手段,

NDK 有两个主要作用:
  • 帮助开发者快速开发 C/C++ 的动态库
  • NDK 使用了交叉编译器,可以在一个平台上开发出另一个平台的二进制代码

2、Android 中 NDK 的使用

1)首先下载 NDK 的安装包

在 SDK Tools 里下载 NDK、LLDB、CMake

手把手教你搭建 NDK 环境搭建_第1张图片

  • NDK : 即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下
  • CMake : 一个跨平台的编译构建工具,可以用简单的语句来描述所有平台的安装过程
  • LLDB : 一个高效的 C/C++ 的调试工具
2)编写界面

这里的界面很简单,一个 TextView 和一个 Button ,点击 Button 后调用 JNI 的方法修改 TextView 的值。

<?xml version="1.0" encoding="utf-8"?>        
3)编写 Activity 代码
/** Copyright (c) 2019\. Lorem ipsum dolor sit amet, consectetur adipiscing elit.* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.*                                              Vestibulum commodo. Ut rhoncus gravida arcu.*/package com.learnandroid.learn_android.JNIDemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import com.learnandroid.learn_android.R;public class JNIActivity extends AppCompatActivity {    private TextView jni_tv;    private Button jni_btn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_jni);        jni_tv = findViewById(R.id.jni_tv);        jni_btn = findViewById(R.id.jni_btn);        jni_btn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                jni_tv.setText(JNIUtils.sayHelloFromJNI());            }        });    }}
4)编写 JNIUtils
package com.learnandroid.learn_android.JNIDemo;public class JNIUtils {    static {        System.loadLibrary("MyJNIHello");    }    public static native String sayHelloFromJNI();}

这里的静态代码块中首先加载了需要使用的动态库,然后创建 native 方法。

5)生成头文件

在 Android Studio 的 Terminal 中,cd 到当前项目的根目录,然后执行 javah 命令

(注意将含有本地方法的类写完整的包名)

# binguner @ binguner in ~/AndroidStudioProjects/learn_android/app/src/main/java [10:12:17]$ javah -d ../cpp com.learnandroid.learn_android.JNIDemo.JNIUtils

-d ../cpp 指定了头文件的生成位置:当前目录上一级下的 cpp 文件夹中。

然后打开 Project 目录下的 com_learnandroid_learn_android_JNIDemo_JNIUtils.h 可以看到它为我们生成的方法原型

/* DO NOT EDIT THIS FILE - it is machine generated */#include /* Header for class com_learnandroid_learn_android_JNIDemo_JNIUtils */#ifndef _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils#define _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils#ifdef __cplusplusextern "C" {#endif/** Class:     com_learnandroid_learn_android_JNIDemo_JNIUtils* Method:    sayHelloFromJNI* Signature: ()Ljava/lang/String;*/JNIEXPORT jstring JNICALL Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI  (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif
6)实现 C++ 代码

在 cpp 目录下新建一个 C++ 文件,命名为 MyJNIDemo

手把手教你搭建 NDK 环境搭建_第2张图片

这时候如果直接在 C++ 文件里编写代码,是没有代码提示的,这时候需要用 CMake 工具。

首先编辑 app 的 build.gradle 文件,添加如下内容

android -> defaultConfig 下添加

// 使用Cmake工具externalNativeBuild {    cmake {        cppFlags ""        //生成多个版本的so文件,指定需要编译的cpu架构        abiFilters "armeabi-v7a"    }}

android -> 下添加

// 配置CMakeLists.txt路径externalNativeBuild {    cmake {        //编译后so文件的名字        **path "src/main/cpp/CMakeLists.txt"**    }}

再编辑 CMakeLists.txt 文件(cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中)

# 设置 CMake 的最低版本cmake_minimum_required(VERSION 3.4.1)# 第一个参数:创建并命名一个 lib,会自动生成这个 lib 的 so 库,# 第二个参数:将它设置为 STATIC (静态库,以 .a 结尾)或者 SHARED(动态库以 .so 结尾),# 最后一个参:数提供一个相对的源码路径# 可以用 add_library 设置多个 lib,CMake 会自动构建并把 lib 包打包到 apk 中。add_library( # Sets the name of the library.        MyJNIHello        # Sets the library as a shared library.        SHARED        # Provides a relative path to your source file(s).        MyJNIHello.cpp        )# 搜索一个预置的 lib,并把它的路径保存为一个变量# CMake 默认在搜索路径中包含了系统的 lib,我们只需要给想要添加的 NDK lib 设置一个名称即可。# CMake 会在完成构建之间检查它是否存在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.        MyJNIHello        # Links the target library to the log library        # included in the NDK.        ${log-lib}        )

这时候点击 Sync Now,编写 C++ 文件有代码提示了

C++ 的代码如下

//// Created by Binguner on 2019-08-13.//#include //#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"extern "C"JNIEXPORT jstring JNICALLJava_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI        (JNIEnv *env, jclass jclass1) {    return env->NewStringUTF("JNIHELLO");}

这里实现了 Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI 方法,并返回了一个 「JNIHELLO」的字符串,
我为什么注释掉 #include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h” 呢?

因为 JNI 生成的头文件目的是帮助我们得到 native 方法的原型,自己写原型容易的话命名可能出错,你也可以看到这个方法的名称非常复杂。但是如果我们能自己写对这个名字,不用 include 这个头文件也可以,CMake 会我们自动生成这个方法。

比如我又在 JNIUtils 中添加了一个 test1() 的方法

手把手教你搭建 NDK 环境搭建_第3张图片

点击代码提示之后,它会自动在 C++ 代码中生成相应的方法,我们只要实现其中的方法即可

手把手教你搭建 NDK 环境搭建_第4张图片

7)运行 App

手把手教你搭建 NDK 环境搭建_第5张图片

 点击按钮后 手把手教你搭建 NDK 环境搭建_第6张图片

8)JNI 中打印日志

在 C++ 文件中添加如下内容:

//// Created by Binguner on 2019-08-13.//#include #include //#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"#define LOG_TAG "System.out.c"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)extern "C"JNIEXPORT jstring JNICALLJava_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI        (JNIEnv *env, jclass jclass1) {    LOGD("TAGD,a=%d,b=%d",1,2);    return env->NewStringUTF("JNIHELLO");}

运行效果

欢迎关注本文作者:

扫码关注并回复「干货」,获取我整理的千G Android、iOS、JavaWeb、大数据、人工智能等学习资源。

更多相关文章

  1. Android HttpClient上传文件与Httpconnection知识小结
  2. iphone/android比较学习之──图片、文件、字符串
  3. Android studio获取证书指纹 (SHA1)的方法
  4. 关于build.gradle配置文件详细参数讲解
  5. eclipse android 设置及修改生成apk的签名文件
  6. Android 解析XML文件方法
  7. Android中读写文件

随机推荐

  1. 教你如何6秒钟往MySQL插入100万条数据的
  2. MySQL如何优雅的备份账号相关信息
  3. MySQL Aborted connection告警日志的分析
  4. MySQL为什么要避免大事务以及大事务解决
  5. 聊一聊MyISAM和InnoDB的区别
  6. MySQL创建数据表时设定引擎MyISAM/InnoDB
  7. Mysql深入探索之Explain执行计划详析
  8. MySQL 8.0统计信息不准确的原因
  9. MySQL如何快速导入数据
  10. MySQL备份脚本的写法