Android NDK 编译工具ndk-build的使用

1.创建native相关方法

新建 JNIUtils.java,并创建native相关方法

public class JNIUtils {

    /**
     * 通过JNI从C中获取数据
     *
     * @return String
     */
    public static native String getDataFromC();

    /**
     * 通过JNI从C++中获取数据
     *
     * @return String
     */
    public static native String getDataFromCPP();
}

1.1 native相关方法去掉报红

取消检测即可,打开 Settings>Editor>Inspections>Android>Missing JNI function 去掉勾选。

去掉后,效果如下:

2.创建c/c++文件

2.1 生成头文件

  1. Terminal终端,通过下面命令 切换到项目xx\app\目录下。
cd D:\Brainbg\AndroidNotes\NDK-First\app

注:由于下面的路径都比较长,我们可以右击相应的文件目录进行快捷复制:

  1. 根据java文件生成c的头文件, 执行如下命令

格式:javah ­d jni -encoding utf-8 ­classpath /java文件夹路径 包名+类名

javah ­d jni classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils
或者
javah ­d jni -encoding utf-8 ­classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils

其中

  • javah :生成头文件
  • ­d jni :当前目录下创建jni文件夹
  • -encoding utf-8 :指定编码格式为utf-8
  • ­classpath D:\Workspace\NDKFirst\app\src\main\java\ :到java目录的路径
  • com.brainbg.ndkfirst.JNIUtils :包名+类名

注:不加上-encoding utf-8,可能会提示错误: 编码GBK的不可映射字符

  1. 执行后,收缩app目录后重新打开,会发现多了一个jni的目录,com_brainbg_ndkfirst_JNIUtils.h就是新生成的头文件。

com_brainbg_ndkfirst_JNIUtils.h内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_brainbg_ndkfirst_JNIUtils */

#ifndef _Included_com_brainbg_ndkfirst_JNIUtils
#define _Included_com_brainbg_ndkfirst_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_brainbg_ndkfirst_JNIUtils
 * Method:    getDataFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC
  (JNIEnv *, jclass);

/*
 * Class:     com_brainbg_ndkfirst_JNIUtils
 * Method:    getDataFromCPP
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

其中,里面的2个方法Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCJNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP是重点,后面需要直接复制进C/C++的文件中进行修改,同时,我们也能发现它生成的格式:

格式:Java_包名_类名_方法名

2.2 添加 c/c++文件

在jni文件夹中添加相应的文件,同时复制上面头文件生成的方法修改。

  • 添加c文件

firstc.c

#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"

JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC(JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "This is my first jni from C!");
}
  • 添加cpp文件

firstcpp.cpp

#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"

JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP(JNIEnv *env, jclass jclass) {
    return env->NewStringUTF("This is my first jni from CPP!");
}

修改好的内容后,你会留意到上面还有提示:大意就是目前的c/c++文件还不属于项目中的一部分!为此,我们还需要处理build.gradle、Android.mk等文件。

3.添加mk文件

3.1 添加 Android.mk文件(必加)

注意mk文件里面不能添加注释,不然编译不通过。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE        := first-jni
LOCAL_SRC_FILES     := firstc.c firstcpp.cpp
include $(BUILD_SHARED_LIBRARY)
  • LOCAL_PATH := $(call my-dir):此变量表示源文件在开发树中的位置。在这行代码中,编译系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件本身所在的目录)的路径。
  • include $(CLEAR_VARS):其值由编译系统提供。CLEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。请注意,GNU Makefile 不会清除 LOCAL_PATH。此变量必须保留其值,因为系统在单一 GNU Make 执行环境(其中的所有变量都是全局变量)中解析所有编译控制文件。在描述每个模块之前,必须声明(重新声明)此变量。
  • LOCAL_MODULE:LOCAL_MODULE 变量存储您要编译的模块的名称。请在应用的每个模块中使用一次此变量。每个模块名称必须唯一,且不含任何空格。编译系统在生成最终共享库文件时,会对您分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 libfirst-jni.so 的库。
  • LOCAL_SRC_FILES:多个文件以空格分隔开,变量必须包含要编译到模块中的 C 和/或 C++ 源文件列表。
  • nclude $(BUILD_SHARED_LIBRARY):帮助系统将所有内容连接到一起,变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要编译的内容以及编译方式。

注意:LOCAL_MODULE中,如果模块名称的开头已经是 lib,则编译系统不会附加额外的 lib 前缀;而是按原样采用模块名称,并添加 .so 扩展名。因此,比如原来名为 libfoo.c 的源文件仍会生成名为 libfoo.so 的共享对象文件。此行为是为了支持 Android 平台源文件根据 Android.mk 文件生成的库;所有这些库的名称都以 lib 开头。

更多内容,可以直接查看:Android.mk 官方介绍

3.2 添加 Application.mk文件(可选)

APP_PLATFORM := android-16
APP_ABI :=all
  • APP_PLATFORM :指定so库所支持最低的API,即 会声明编译此应用所面向的 Android API 级别,并对应于应用的 minSdkVersion

注: 如果未指定,ndk-build 将以 NDK 支持的最低 API 级别为目标。最新 NDK 支持的最低 API 级别总是足够低,可以支持几乎所有使用中的设备。

警告:将 APP_PLATFORM 设置为高于应用的 minSdkVersion 可能会生成一个无法在旧设备上运行的应用。在大多数情况下,库将无法加载,因为它们引用了在旧设备上不可用的符号。

  • APP_ABI:指定生成平台的so库

默认情况下,NDK 编译系统会为所有非弃用 ABI 生成代码。您可以使用 APP_ABI 设置为特定 ABI 生成代码。

指令集
32 位 ARMv7APP_ABI := armeabi-v7a
64 位 ARMv8 (AArch64)APP_ABI := arm64-v8a
x86APP_ABI := x86
x86-64APP_ABI := x86_64
所有支持的 ABI(默认)APP_ABI := all

ABI 和支持的指令集,可以查看Google的ABI 管理,其中有相关内容如下:

注:不添加Application.mk,会提示Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.

更多内容,可以直接查看:Application.mk 官方介绍

4.编译so库文件

进入app目录,执行ndk-build进行编译

cd D:\Workspace\NDKFirst\app

ndk-build

执行成功后,效果如下

同时项目中会得到相应的so包,其中lib为核心,obj为编译中产生的文件,可删除。

4.用Gradle链接c++项目

  1. jni目录中右击任意文件选择Link C++ project with Gradle

  1. 其中Build System 选择ndk-build ,Project Path 选择Android.mk的路径,而后确认。

  1. 完成上面的操作后,app/build.gradle里面会出现如下代码

  • build.gradle
android {
   ......

   externalNativeBuild {
        ndkBuild {
            path file('jni/Android.mk')
        }
    }
}

当然,下次项目的话,我们也可以直接加入上面代码即可。

6.加载so库、运行app

  • JNIUtils
public class JNIUtils {
    public static final String TAG = JNIUtils.class.getSimpleName();

    static {
        try {
            System.loadLibrary("first-jni");  //加载so库
        } catch (UnsatisfiedLinkError e) {
            e.printStackTrace();
            Log.e(TAG, "loadLibrary fail !");
        }
    }

    /**
     * 通过JNI从C中获取数据
     *
     * @return String
     */
    public static native String getDataFromC();

    /**
     * 通过JNI从C++中获取数据
     *
     * @return String
     */
    public static native String getDataFromCPP();
}

  • MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tvC = findViewById(R.id.tv_c);
        TextView tvCPP = findViewById(R.id.tv_cpp);
        tvC.setText(JNIUtils.getDataFromC());
        tvCPP.setText(JNIUtils.getDataFromCPP());
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_c"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="c-content" />

    <TextView
        android:id="@+id/tv_cpp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="cpp-content" />
</LinearLayout>
  • 运行后效果

到此为止,第一个关于NDK、JNI的Demo已经完成,相关文章,后续可能、应该、大概也会推出吧。

7.下载地址

GitHub项目下载

参考资料

https://developer.android.google.cn/ndk/guides
https://blog.youkuaiyun.com/young_time/article/details/80346631
https://yq.aliyun.com/articles/60710?spm=a2c4e.11153940.0.0.11bc68d9CLrDix
https://www.jianshu.com/p/87ce6f565d37

作者:Brainbg(白雨)
GitHub:https://github.com/Brainbg
博客:https://www.brainbg.com/
简书:https://www.jianshu.com/u/94518ede7100
优快云:https://blog.youkuaiyun.com/u014720022

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值