【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對象,直接調用就行了,主要是驗證下結果
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章