手把手教你搭建 NDK 环境搭建
本文基于 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 : 即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下
- CMake : 一个跨平台的编译构建工具,可以用简单的语句来描述所有平台的安装过程
- LLDB : 一个高效的 C/C++ 的调试工具
2)编写界面
这里的界面很简单,一个 TextView 和一个 Button ,点击 Button 后调用 JNI 的方法修改 TextView 的值。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".JNIDemo.JNIActivity"> <TextView android:id="@+id/jni_tv" android:layout_marginTop="20dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:text="I'm a TextView" android:textAllCaps="false" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/jni_btn" app:layout_constraintTop_toBottomOf="@id/jni_tv" android:layout_marginTop="20dp" app:layout_constraintStart_toStartOf="parent" android:text="Call Method From JNI" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </android.support.constraint.ConstraintLayout>
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 <jni.h> /* 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 __cplusplus extern "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
这时候如果直接在 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 <jni.h> //#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h" extern "C" JNIEXPORT jstring JNICALL Java_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() 的方法
点击代码提示之后,它会自动在 C++ 代码中生成相应的方法,我们只要实现其中的方法即可
7)运行 App
点击按钮后
8)JNI 中打印日志
在 C++ 文件中添加如下内容:
// // Created by Binguner on 2019-08-13. // #include <android/log.h> #include <jni.h> //#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 JNICALL Java_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、大数据、人工智能等学习资源。