前言:

本文章以Android Studio为IDE,以手动编译不使用cmake的方式为例,在某个已有普通android项目基础上,创建jni程序并运行。详细介绍以下内容:

1、环境配置(Android Studio、SDK、JDK、NDK、Gradle)

2、创建java中的jni程序

3、根据java中的jni程序生成头文件,创建c/c++文件,创建配置文件

4、打包生成 *.so 库文件

5、配置项目引用创建好的 *.so 库文件并运行

6、External Tools 工具的配置和使用

准备好了吗?现在开干!

1、环境配置:首先电脑环境为 Windows10 64位操作系统,mac系统暂不涉及。由于Android Studio与Gradle,SDK,NDK,JDK的版本兼容问题,特意使用如下版本进行配置

Android Studio 4.0.2

SDK 30

NDK 21.3.6528147

JDK 1.8

1.1、Android Studio

        Android Studio 下载文件归档  |  Android 开发者  |  Android Developers (google.cn)

        进入网站,滚动到底部,点击“我同意这些条款”

         然后在浏览器打开的界面中搜索 4.0.2

        下载并安装好Android Studio后,第一次打开可能报错,不要急,复制错误内容搜索一下,很简单就能解决。正常打开Android Studio后,新建一个项目或者导入一个已有项目,在此不做过多说明。

1.2、SDK:这里只对我的SDK版本做说明,不代表其他版本不可

1.3、JDK:这里我使用JDK 1.8,不代表其他版本不可

        官网下载地址:Java Downloads | Oracle

        

1.4、NDK:具体的NDK配置流程可以自行查询,网上很多。我使用的NDK版本为 21.3.6528147

        

1.5、Gradle:其他的Gradle版本未测试,这里贴出我使用的构建版本

1.5.1、gradle-wrapper.properties

        打开文件,配置项如下

        distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip        

1.5.2、Project -> build.gradle

        打开文件,配置项如下

        classpath 'com.android.tools.build:gradle:4.0.2'

   

2、创建java中的jni程序:先创建一个类,在类中分别加入“静态加载so文件的程序”和“定义的native方法”

        注意:包名类名方法名定义好后,不要随意更改,后面需要生成对应的头文件

package com.ndktest.jnilib;

/**
 * jni方法工具类
 *
 * @author Wesley Yan
 */
public class MyJniTest {

    static {
        System.loadLibrary("jniTestSo");
    }

    /**
     * 计算 a+b 的和
     * @param a int
     * @param b int
     *
     * @return int 返回 a+b 的结果值
     */
    public static native int getAddData(int a, int b);

    /**
     * 获取so库中的字符串
     */
    public static native String getStringFromJni();

}

3、根据java中的jni程序生成头文件,创建c/c++文件,再创建两个配置文件Application.mk、Android.mk

3.1、根据 MyJniTest 类生成对应的头文件

        在terminal中cd到 app/src/main/java 目录下

        

        执行以下命令

        javah -encoding utf-8 -d ../jni -jni com.ndktest.jnilib.MyJniTest

        正确执行后,切换Android Studio中的导览图类型为project,在src/main/java同级别的jni目录下就能看到生成的头文件 com_ndktest_jnilib_MyJniTest.h

        

         打开 com_ndktest_jnilib_MyJniTest.h 文件,可以看到里面会定义出我们定义的native方法所对应的头文件中的方法,这里不需要做任何修改     

3.2、根据头文件创建对应的 c/c++ 文件:

        在头文件所在的jni目录:右键 -> New -> File

        命名为 demo.c

        在 demo.c 文件中导入头文件,加入头文件中方法的实现

//
// Created by wct on 2023/5/8.
//

//#include <jni.h>
#include "com_ndktest_jnilib_MyJniTest.h"

/*
 * Class:     com_ndktest_jnilib_MyJniTest
 * Method:    getAddData
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL
Java_com_ndktest_jnilib_MyJniTest_getAddData(JNIEnv *env, jclass thiz, jint a, jint b) {
    return a + b;
}

/*
 * Class:     com_ndktest_jnilib_MyJniTest
 * Method:    getStringFromJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_com_ndktest_jnilib_MyJniTest_getStringFromJni(JNIEnv *env, jclass thiz) {
    return (*env)->NewStringUTF(env, "Hello World from c++");
}

3.3、创建对应的配置文件 Application.mk

        在头文件所在的jni目录:右键 -> New -> File

        命名为 Application.mk

        再把相关配置属性加入其中,可直接复制下面的代码到 Application.mk

# Application.mk 参数


# 默认生成支持的多种类型.so
APP_ABI := all

# APP_PLATFORM := android-16不配置,打包.so会出错
APP_PLATFORM := android-16

# 可选项,如果没有定义,则NDK编译所有Android.mk中的modules.如果定义了,则只编译Android.mk中被APP_MODULES指定的模块以及他们所依赖的模块。
APP_MODULES := jniTestSo

3.4、创建对应的配置文件 Android.mk

        在头文件所在的jni目录:右键 -> New -> File

        命名为 Android.mk

        再把相关配置属性加入其中,可直接复制下面的代码到 Android.mk

# Android.mk 参数



# 设置工作目录,它用于在开发tree中查找源文件。宏my-dir由Build System提供,会返回Android.mk文件所在的目录
LOCAL_PATH := $(call my-dir)

# CLEAR_VARS变量由Build System提供。指向一个指定的GNU Makefile,由它负责清理LOCAL_xxx类型文件,但不是清理LOCAL_PATH
# 所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响,这一操作必须有
include $(CLEAR_VARS)

# LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块,名字必须唯一且不包含空格
# Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabc,则生成libabc.so。不再添加前缀。
LOCAL_MODULE := jniTestSo

# 指定参与模块编译的C/C++源文件名。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp
LOCAL_SRC_FILES =: demo.c

# BUILD_SHARED_LIBRARY是Build System提供的一个变量,指向一个GUN Makefile Script。它负责收集自从上次调用include $(CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型
# 1. BUILD_STATIC_LIBRARY:编译为静态库
# 2. BUILD_SHARED_LIBRARY:编译为动态库
# 3. BUILD_EXECUTABLE:编译为Native C 可执行程序
# 4. BUILD_PREBUILT:该模块已经预先编译
include $(BUILD_SHARED_LIBRARY)

4、打包生成 *.so 库文件:这里我在Android.mk中配置属性 LOCAL_MODULE := jniTestSo,因此生成的so文件名为 libjniTestSo.so,生成的方式如下

         在terminal中cd到 app/src/main/java 目录下

        

        执行以下命令

        ndk-build    

        正确执行后,切换Android Studio中的导览图类型为project,在src/main/java同级别的libs目录下就能看到生成的两个文件夹 libs 和 obj,其中src/main/libs文件夹下就能看到不同ABI对应的so库文件

5、配置已有项目引用创建好的 *.so 库文件并运行:

5.1、复制生成的so库文件到项目指定目录:

        找到设备对应可用的ABI类型,这里我的设备支持的ABI为 arm64-v8a,因此只需要把 src/main/libs 下的 arm64-v8a 文件夹拷贝到 app/libs 目录中,如果你的设备支持其他的ABI类型,复制对应的文件即可

5.2、打开 Module 中的 build.gradle 文件,在 buildTypes 中增加 sourceSets 配置

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

        // 加载本地so文件配置(必选)
        sourceSets {
            main {
                jniLibs.srcDir(['libs'])
            }
        }
    }

5.3、打开 gradle.properties 文件,添加 android.useDeprecatedNdk 属性

# 添加对旧版本的NDK支持,此项目使用NDK版本=21.3.6528147,不配置以下参数也可
android.useDeprecatedNdk=true

5.4、测试调用 MyJniTest 类中的 jni 方法

// 测试jni调用so库方法
int getAddData = MyJniTest.getAddData(2, 6);
String getStringFromJni = MyJniTest.getStringFromJni();
textView_message.setText("getAddData=" + getAddData + "\ngetStringFromJni=" + getStringFromJni);

5.5、运行程序:我这里使用Android Studio连接真机直接运行

上图中输出的结果分别对应我在 demo.c 文件中的方法实现

6、External Tools 工具的配置和使用:配置一次,终生幸福(借用前人的一句话)

        在上述 3.1生成头文件 和 4生成so库文件 的过程中,都使用到了Android Studio自带的命令行工具,分别执行了 javah 和 ndk-build 命令。Android Studio 还为我们提供了另一种执行终端命令的工具,就是External Tools,它可以帮助我们更简单的实现 3.1 和 4 中的工作。

6.1、配置 External Tools -> javah

点击 Android Studio 中的 File -> settings,在打开面板的搜索栏中输入 External Tools,选中Tools 下的 External Tools,再点击 +号,填写如下配置,再点击 “OK”按钮保存

Program:
// 这里改为你的 JDK 中 bin 目录下面的 javah.exe 文件目录
C:\Program Files\Java\jdk-1.8\bin\javah.exe

Arguments:
// 不用改,直接复制即可
-encoding UTF-8 -d ../jni -jni $FileClass$

Working directory:
// 不用改,直接复制即可
$ProjectFileDir$\app\src\main\java

6.2、配置 External Tools -> ndk-build

继续点击 +号,填写如下配置,再点击“OK”按钮保存

Program:
// 这里改为你的 NDK 对应的 ndk-build.cmd 文件目录
$ModuleSdkPath$/ndk-bundle/21.3.6528147/ndk-build.cmd

Arguments:
// 指定输出so库文件的目录,不用改,直接复制即可
NDK_LIBS_OUT=$ProjectFileDir$\app\src\main\libs

Working directory:
// 不用改,直接复制即可
$ProjectFileDir$\app\src\main

6.4、使用 External Tools -> ndk-build

        在上述 3.1 生成头文件时,右键点击要生成头文件对应的类,然后依次选中 

        External Tools -> javah

6.3、使用 External Tools -> ndk-build

        在上述 4 生成so文件时,右键点击 src/main 下面的 jni 目录,然后依次选中 

        External Tools -> ndk-build

 

        写在最后,如果你跟着上面的步骤,运行出了自己的jni程序,相信过程中也一定有所收获,码字不易,与君共勉!

        

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐