【NDK】【006】AndroidStudio编译和调用so库

前面我们已经讲解过,如何在Java中使用JNI和DLL,这和在Android中使用JNI和SO原理是完全一致的,流程也相似,只是编译的平台和工具不同
如果我们前面都学透了,现在就会很轻松了。下面我们开始讲解,如何在Android中使用JNI

安装NDK开发环境

Tools - SDK Manager - 安装CMake,NDK,LLDB等组件
在这里插入图片描述
编写Java调用JNI的接口
在这里插入图片描述


	package com.easing.android;
	
	public class JniHello {
	
	    static {
	        System.loadLibrary("hello");
	    }
	
	    public native void hello();
	
	    public native void printMessage(String message);
	
	    public native int sum(int a, int b);
	}

根据Java接口生成JNI头文件

在java目录下打开Terminal面板,输入javah com.easing.android.JniHello指令,根据Java接口自动生成对应的JNI头文件
在这里插入图片描述


	#include <jni.h>
	
	#ifndef _Included_com_easing_android_JniHello
	#define _Included_com_easing_android_JniHello
	extern "C" {
	    JNIEXPORT void JNICALL Java_com_easing_android_JniHello_hello(JNIEnv *, jobject);
	    JNIEXPORT void JNICALL Java_com_easing_android_JniHello_printMessage(JNIEnv *, jobject, jstring);
	    JNIEXPORT jint JNICALL Java_com_easing_android_JniHello_sum(JNIEnv *, jobject, jint, jint);
	}
	#endif

编写CPP实现JNI头文件

在src下新建一个jni目录,专门用来编写c++代码
将刚才生成的头文件剪切到jni目录下,创建一个cpp文件,实现头文件中的接口
在这里插入图片描述


	#include <com_easing_android_JniHello.h>
	#include <android/log.h>
	#include <jni.h>
	#include <stdio.h>
	
	#define LOG_TAG "JniHello"
	#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
	#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
	#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
	
	extern "C" JNIEXPORT void JNICALL Java_com_easing_android_JniHello_hello(JNIEnv *env, jobject obj) {
	    LOGD("JniHello");
	}
	
	extern "C" JNIEXPORT void JNICALL Java_com_easing_android_JniHello_printMessage(JNIEnv *env, jobject obj, jstring message) {
	    const char *str = env->GetStringUTFChars(message, NULL);
	    LOGI(str);
	}
	
	extern "C" JNIEXPORT jint JNICALL Java_com_easing_android_JniHello_sum(JNIEnv *env, jobject obj, jint a, jint b) {
	    return a + b;
	}

NDK出于安全性的考虑,默认限制了jstring到char*的转换,要经过适当配置才能使用,这个我们下篇博客我们再细讲

编写Android.mk和Application.mk文件

c++代码想编译成so库,必须通过Android.mk和Application.mk来进行配置
Android.mk指定so库名称,要编译的文件
Application.mk则指定要适配的CPU架构,最低的安卓系统版本
关于这两者更详细的配置,我们后面再细讲,现在重要的学会调用JNI完整流程
在这里插入图片描述

	
	#Android.mk
	
	LOCAL_PATH := $(call my-dir)
	include $(CLEAR_VARS)
	LOCAL_MODULE := libhello
	LOCAL_LDLIBS    := -lm -llog
	LOCAL_SRC_FILES := com_easing_android_JniHello.cpp
	include $(BUILD_SHARED_LIBRARY)

	
	#Application.mk
	
	APP_ABI := all
	APP_PLATFORM := android-23

设置支持的CPU架构

不同的CPU架构需要不同的so文件,如果我们要支持所有CPU架构的话,就需要编译并打包多个so文件,这样会大幅增加安装包体积
所以一般我们只会支持用户量最大(arm64-v8a)或兼容性最好(armeabi-v7a)的CPU架构
现在的新机子一般都是arm64-v8a架构的,使用arm64-v8a库运行效率最高
但是很多旧机子或旧的so库都是armeabi-v7a版本的,为了兼容一般都会添加armeabi-v7a支持
arm64-v8a机型也是可以使用armeabi-v7a的库的,如果不在意性能损失,可以只添加armeabi-v7a的so库,这样体积比较小,so包较少管理也方便

支持哪些CPU架构可以通过Gradle中的abiFilters选项来配置


	android {
	
	    defaultConfig {
	
	        //过滤CPU架构,只使用armv7的库
	        ndk {
	            abiFilters "armeabi-v7a"
	        }
	    }
	
	}

开启Gradle中的JNI编译选项

c++代码和mk文件编写完成后,我们有两种方式来使用jni代码
一种是让gradle自动将jni代码编译成为so库并引用
一种是通过ndk指令手动将其编译为so库,手动添加引用
第一种方式由于可以对c++代码自动编译自动引用,很适合经常修改c++源码的情景
第二种方式需要手动编译,麻烦一点,如果需要将so库分享给别人使用,则必须这么做

如果我们想Gradle帮我们自动编译C++代码,需要开启externalNativeBuild选项,并指定Android.mk位置
编译成功后的so库存放在build/intermediates/ndkBuild目录下,可以直接拷贝出来供其它用途
在这里插入图片描述


	android {
		
	    defaultConfig {
	    
	    }
	
	    //编译jni目录,开启这个选项后,会自动编译C++代码生成so文件,并自动引用
	    //开启此选项后,就不需要通过sourceSets选项来指定so库位置了,否则会发生so库冲突
	    externalNativeBuild {
	        ndkBuild {
	            path 'src/jni/Android.mk'
	        }
	    }
	
	}

在Gradle中指定so库加载目录

如果想直接使用编译好的so库,可以通过Gradle中的sourceSets选项来指定so库的加载位置
但是要注意,同样的so文件,不能既通过externalNativeBuild来编译,又通过sourceSets来引用
这样会有两份同名的so库,造成冲突,从而最终无法编译通过
在这里插入图片描述


	android {
	
	    defaultConfig {
	
	    }
	
	    //加载指定位置的so库
	    sourceSets {
	        main {
	            jniLibs.srcDirs = ['libs']
	        }
	    }
	
	}

使用NDK编译SO库

我们可以使用externalNativeBuild指令来编译出so文件,但这样生成的so文件会被直接使用
我们也可以手动执行ndk指令来编译so文件,这样可以自己决定如何使用

在jni目录下打开Terminal面板,输入ndk-build指令,就会在jni同级目录生成一个名为libs的so库文件夹
在这里插入图片描述
在Java代码中使用JNI接口

这个就简单了,new出一个JniHello对象,直接调用就行了,主要是验证下结果
在这里插入图片描述
在这里插入图片描述

发布了442 篇原创文章 · 获赞 45 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/u013718730/article/details/104488216