Android 使用ndk-build指令编译so库为项目添加C支持以实现Java层调用Native代码

一、前言

在项目中添加第三方依赖的时候,经常会使用到它的so文件,可一直没有去想过so文件是怎么编译出来的,编译出来的so文件又是怎么供别的项目使用的,今天趁着闲余时间,准备查查资料好好研究一下这块。因为我现在使用的是android studio2.3.3,ndk-r16b,所以在编写过程中,可能会存在差异,不过应该问题不大。

二、环境搭建

在正式开始之前,如果你的电脑还没有配置NDK环境,那么请先去下载NDK吧,或者你也可以使用AS在Setting=>System Settings=>Android SDK=>SDK Tools中勾选NDK下载,下载之后解压文件,例如我将解压后的文件放在了D盘的ndk目录之下

D:\ndk\android-ndk-r16b

随后配置一下环境变量,我的电脑=>属性=>高级系统设置=>环境变量,在PATH中添加ndk路径

D:\ndk\android-ndk-r16b;

在我的File=>Project Structure=>SDK Location=>Android NDK Location,填上我们刚才的ndk路径,之后点击ok

D:\ndk\android-ndk-r16b

在项目中的local.properties文件中添加

ndk.dir=D\:\\ndk\\android-ndk-r16b

在gradle.properties该文件中添加过时ndk版本支持

Android.useDeprecatedNdk=true

三、编译so库并加载到项目中

这是我们编写的java类,并添加了native方法

public class MyJniUtils {

    //获取一个最简单的helloWord字符串
    public static native String getHelloWorld();
}

我们 Build=>Make Project,并找到MyJniUtils编译出来的class文件,本地所在目录如下:

这里写图片描述

我们View=>Tool Windows=>Terminal打开终端,并cd到上图目录结构中debug目录之下:

E:\MyJniTest>cd app/build/intermediates/classes/debug

使用javah命令生成MyJniUtils.class的JNI的头文件

javah zmj.myjnitest.MyJniUtils

上面两步的图例如下

这里写图片描述

你会发现在debug目录下生成了zmj_myjnitest_MyJniUtils.h头文件,我们将该头文件复制到当前项目下的jni目录,如果没有则新建,jni所在目录如下

这里写图片描述

在jni目录下编写一个.c文件实现该JNI头文件中的函数,c文件名字自己随意定义,本例中为MyJniTest.c,也可以使用cpp的实现文件,但两者在代码编写上有差异

#include <zmj_myjnitest_MyJniUtils.h>
JNIEXPORT jstring JNICALL Java_zmj_myjnitest_MyJniUtils_getHelloWorld (JNIEnv *env, jobject jObj){
    return (*env)->NewStringUTF(env, "Hello World");
}

编写Android.mk文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE    := myJniTest
LOCAL_SRC_FILES := MyJniTest.c

include $(BUILD_SHARED_LIBRARY)

编写Application.mk文件

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := all
APP_PLATFORM := android-14

jni目录结构图如下

这里写图片描述

利用ndk-build生成动态链接库,将终端cd 到jni目录之下,执行ndk-build指令,编译出该动态so库

这里写图片描述

编译完成之后,你会发现,libs目录下已经自动生成了.so文件,并在main目录下自动生成了obj文件夹

这里写图片描述

在build.gradle中,android{ }内添加

sourceSets {
        main() {
            jniLibs.srcDirs = ['src/main/libs']
            jni.srcDirs = [] //屏蔽掉默认的jni编译生成过程
        }
    }

在我们的HomePageActivity类中加载刚刚编译好的so库

public class HomePageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home_page);
        Log.i("Tag",MyJniUtils.getHelloWorld());
    }

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

到这里我们就已经介绍完了生成so文件的过程,也知道了怎么去实现java层调用Native代码,运行下我们的项目,结果如下

这里写图片描述

四、其他项目中使用该so库

新建一个新项目,将我们so文件放置到该项目的相应libs目录下,其实最好放在项目main目录下的jniLibs文件夹之下,这两者在build.gradle的内容稍有不同,前者需要指定jniLibs.srcDirs = [‘libs’]

这里写图片描述

在java目录下新建zmj.myjnitest包(zmj.myjnitest为我们编译so文件所在的项目包名),之后将我们的MyJniUtils类复制到zmj.myjnitest目录下(记得要跟so文件所在项目内的MyJniUtils类路径一致),目录结果图如下

这里写图片描述

在我们需要的地方调用

public class MyJniTestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_jni_test);
        Log.i("Tag", MyJniUtils.getHelloWorld());
    }

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

在build.gradle的android{ }内添加

sourceSets {
        main() {
            jni.srcDirs = []
            jniLibs.srcDirs = ['libs']//如果将libs文件下的一系列arm文件夹,放在项目main目录下的jniLibs文件夹里,则不需要
        }
    }

最后运行一下我们的程序,结果如下

这里写图片描述

五、总结

到这里我们就已经介绍完了最简单的JNI的使用,其实JNI的调用方式主要有两种,一种是Java代码调用Native的代码,这也是最常见的,另一种则恰好相反,就是在Native层调用Java代码,这种方式如果以后有机会会另行介绍。还有在本文中没有对.c和.mk文件中的每一行进行含义说明,也没有介绍JNI和NDK的相关概念,我会在后面博客中详细解释,本文最重要的是把该项目运行起来。

demo下载

猜你喜欢

转载自blog.csdn.net/MingJieZuo/article/details/80166184