1.前言
为了更好的理解并应用前面所分享的内容,下面将实例演示JNI开发步骤。
2.Native工程准备
2.1SDK Manager 安装相应组件组件
AS菜单栏中选择 Tools >SDK Manager,点击 SDK Tools 选项卡,勾选 LLDB,CMake 和 NDK。如下图:
image.png
2.2新建工程
打开Android Studio--->File菜单--->New--->New Project然后打开如下对话框
选择Native C++然后Next指定Name,PackageName等Next
c++Standard 标准的C++,默认就好,最后Finish即可创建Native工程。
3.开发步骤
3.1JNI函数注册
3.1.1静态注册方式生成对应的.h头文件
java层编写native方法
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String stringFromJNI2();
public native String fromJaveToNative(String s);
public native String signture(String sig);
public native int add(int a, int b);
public native void throwException(String msg);
public String getPackageN() {
return getPackageName();
}
public void fromNativeJNI() {
Logger.getLogger("MainActivity").severe("fromNativeJNI str>>>>>>>");
}
public void fromNativeJNI2(String str) {
Logger.getLogger("MainActivity").severe("fromNativeJNI str=" + str);
}
public static void fromNativeJNI3(String str) {
Logger.getLogger("MainActivity").severe("fromNativeJNI str=" + str);
}
public static void fromNativeJNI4() {
Logger.getLogger("MainActivity").severe("fromNativeJNI str4444444");
}
public int getValue() {
Logger.getLogger("MainActivity").severe("fromNativeJNI getValue");
return i;
}
public void modifyValue(int j) {
Logger.getLogger("MainActivity").severe("fromNativeJNI str55555>>j=" + j);
}
通过javah生成jni对应的函数格式
/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/javah -classpath . -jni -d /Users/zhouwen/work/NativeApp/app/src/main/jni jni.chowen.com.nativeapp.MainActivity
执行完命令之后,在jni目录下会自动生成jni_chowen_com_nativeapp_MainActivity.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_chowen_com_nativeapp_MainActivity */
#ifndef _Included_jni_chowen_com_nativeapp_MainActivity
#define _Included_jni_chowen_com_nativeapp_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: passBitmap
* Signature: (Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_passBitmap
(JNIEnv *, jobject, jobject);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_jni_chowen_com_nativeapp_MainActivity_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI
(JNIEnv *, jobject);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: stringFromJNI2
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI2
(JNIEnv *, jobject);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: fromJaveToNative
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_fromJaveToNative
(JNIEnv *, jobject, jstring);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: signture
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
(JNIEnv *, jobject, jstring);
/*
* Class: jni_chowen_com_nativeapp_MainActivity
* Method: throwException
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_throwException
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
JNI层实现
新建native-lib.cpp c++文件,并实现javah生成的jni_chowen_com_nativeapp_MainActivity.h以上.h文件函数
#include <jni.h>
#include <string>
#include "jni_chowen_com_nativeapp_MainActivity.h"
#include "jni_chowen_com_nativeapp_CInterface.h"
#include "MD5.h"
#include <android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
using namespace std;
jstring getPackname(JNIEnv *env, jobject clazz, jobject obj);
jstring getSignature(JNIEnv *env, jobject clazz, jobject jobject1);
int RegisterNatives(JNIEnv *env);
//静态注册
//extern "C" JNIEXPORT jstring JNICALL
//Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI(
// JNIEnv *env,
// jobject /* this */) {
// string hello = "Hello from C++";
// jint jint4 = env->GetVersion();
// LOGE("JNI version: %d", jint4);
//
// return env->NewStringUTF(hello.c_str());
//}
//动态注册
static jstring stringFromJNI(
JNIEnv *env,
jobject jobject1/* this */) {
string hello = "Hello from C++";
jint jint4 = env->GetVersion();
LOGE("JNI version: %d", jint4);
return env->NewStringUTF(hello.c_str());
}
jint add(JNIEnv *env, jclass clazz, jint a, jint b) {
return a + b;
}
JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_throwException
(JNIEnv *env, jobject jobject1, jstring jstring1) {
jclass jclass1 = env->FindClass("java/io/IOException");
const char* c = env->GetStringUTFChars(jstring1, 0);
// env->FatalError(c); // 抛出一个致命异常
jthrowable jthrowable1 = env->ExceptionOccurred();
if (env->ThrowNew(jclass1, c) == JNI_OK){
if(jthrowable1){
LOGE("JNI throw suc ExceptionOccurred");
}
env->ExceptionDescribe();//打印exception msg
env->ExceptionClear();
LOGE("JNI throw suc");
} else {
LOGE("JNI throw failed");
}
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jint result = RegisterNatives(env);
LOGE("RegisterNatives result: %d", result);
return JNI_VERSION_1_6;
}
int RegisterNatives(JNIEnv *env) {
jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
if (clazz == NULL) {
LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
return JNI_ERR;
}
JNINativeMethod methods_MainActivity[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
{"add", "(II)I", (void *) add}
};
LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
// int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
return env->RegisterNatives(clazz, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
(JNIEnv *env, jobject jobject1, jstring jstring1) {
//md5加密
const char *jcstr = (env)->GetStringUTFChars(jstring1, 0);
MD5 md5;
md5.update(jcstr);
string jstring2 = md5.toString();
//package_name
jstring packName = getPackname(env, jobject1, jobject1);
const char *c = env->GetStringUTFChars(packName, 0);
LOGE("getPackageName: %s", c);
//signature
jstring signatures = getSignature(env, jobject1, jobject1);
const char *signaturesc = env->GetStringUTFChars(signatures, 0);
LOGE("signatures: %s", signaturesc);
// jstring js3 = (jstring) "jni.chowen.com.nativeapp";
// const char* c33 = env->GetStringUTFChars(js3, 0);
// if (strcmp(c, c33)) {
// LOGE("getPackageName: %s", "is cmp!!!!");
// }
// return (*env)->NewStringUTF(env, jstring2.c_str());
return env->NewStringUTF(jstring2.c_str());
}
jstring getPackname(JNIEnv *env, jobject clazz, jobject obj) {
jclass native_class = env->GetObjectClass(obj);
jmethodID mId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
jstring packName = static_cast<jstring>(env->CallObjectMethod(obj, mId));
return packName;
}
jstring getSignature(JNIEnv *env, jobject clazz, jobject jobject1) {
//PackageInfo packageInfo = getPackageManager().getPackageInfo(
// getPackageName(), PackageManager.GET_SIGNATURES);
//Signature[] signs = packageInfo.signatures;
//Signature sign = signs[0];
jclass native_class = env->GetObjectClass(clazz);
jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager",
"()Landroid/content/pm/PackageManager;");
jobject pm_obj = env->CallObjectMethod(clazz, pm_id);
jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jstring pkg_str = getPackname(env, clazz, clazz);
// 获得应用包的信息
jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 获得 PackageInfo 类
jclass pi_clazz = env->GetObjectClass(pi_obj);
// 获得签名数组属性的 ID
jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures",
"[Landroid/content/pm/Signature;");
jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
jobjectArray signaturesArray = (jobjectArray) signatures_obj;
jsize size = env->GetArrayLength(signaturesArray);
jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
jclass signature_clazz = env->GetObjectClass(signature_obj);
jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString",
"()Ljava/lang/String;");
jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
char *c_msg = (char *) env->GetStringUTFChars(str, 0);
LOGI("signsture: %s", c_msg);
return str;
}
//JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI2
// (JNIEnv *env, jobject js) {
// LOGI("No data on command pipe!");
//
// //抛java层异常
//// jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
//// env->ThrowNew(newExcCls, "throw from JNI");
//
// return env->NewStringUTF("from native str");
//}
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_fromJaveToNative
(JNIEnv *env, jobject jobject1, jstring jstring1) {
//method 1
// jclass jclass1 = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
// method 2
jclass jclass1 = env->GetObjectClass(jobject1);
jmethodID jmethodID2 = env->GetMethodID(jclass1, "<init>", "()V");
jobject jobject2 = env->NewObject(jclass1, jmethodID2);
jmethodID jmethodID1 = env->GetMethodID(jclass1, "fromNativeJNI2", "(Ljava/lang/String;)V");
jstring message = env->NewStringUTF("call instance method");
env->CallVoidMethod(jobject2, jmethodID1, message);
jmethodID jmethodID4 = env->GetMethodID(jclass1, "fromNativeJNI", "()V");
env->CallVoidMethod(jobject2, jmethodID4);
jstring message2 = env->NewStringUTF("call instance method33333");
jmethodID jmethodID3 = env->GetStaticMethodID(jclass1, "fromNativeJNI3",
"(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jclass1, jmethodID3, message2);
jmethodID jmethodID5 = env->GetStaticMethodID(jclass1, "fromNativeJNI4", "()V");
env->CallStaticVoidMethod(jclass1, jmethodID5);
jclass jclass2 = env->FindClass("jni/chowen/com/nativeapp/CInterface");
jmethodID jmethodID6 = env->GetStaticMethodID(jclass2, "setAndGetValue", "(I)I");
jint jint2 = env->CallStaticIntMethod(jclass2, jmethodID6, 2000);
LOGE("AndroidBitmap_getInfo failed, jint2: %d", jint2);
// #修改field
jfieldID jfieldID2 = env->GetFieldID(jclass1, "i", "I");
env->SetIntField(jobject2, jfieldID2, 200);
jfieldID jfieldID1 = env->GetFieldID(jclass1, "i", "I");
jint jint1 = env->GetIntField(jobject2, jfieldID1);
LOGE("AndroidBitmap_getInfo failed, result: %d", jint1);
jmethodID jmethodID7 = env->GetMethodID(jclass1, "getValue", "()I");
jint jint3 = env->CallIntMethod(jobject2, jmethodID7);
LOGE("AndroidBitmap_getInfo failed, jint3: %d", jint3);
env->DeleteLocalRef(jclass1);
env->DeleteLocalRef(message);
env->DeleteLocalRef(jobject2);
env->DeleteLocalRef(message2);
env->DeleteLocalRef(jclass2);
// jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
// env->ThrowNew(newExcCls, "throw from JNI");
return env->NewStringUTF("fromJaveToNative");
}
3.1.2函数动态注册方式
直接在JNI_OnLoad函数里动态注册相关函数,之前讲过JNI_OnLoad的初始化时机,是在System.loadLibrary里加载的,具体请看JNI方法注册及加载原理分析
下面以native-lib.cpp中两个函数为例:
//动态注册
static jstring stringFromJNI(
JNIEnv *env,
jobject jobject1/* this */) {
string hello = "Hello from C++";
jint jint4 = env->GetVersion();
LOGE("JNI version: %d", jint4);
return env->NewStringUTF(hello.c_str());
}
jint add(JNIEnv *env, jclass clazz, jint a, jint b) {
return a + b;
}
//重载JNI_OnLoad函数,在其中可做初始化相关工作
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jint result = RegisterNatives(env);
LOGE("RegisterNatives result: %d", result);
return JNI_VERSION_1_6;
}
//具体注册函数
int RegisterNatives(JNIEnv *env) {
jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
if (clazz == NULL) {
LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
return JNI_ERR;
}
JNINativeMethod methods_MainActivity[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
{"add", "(II)I", (void *) add}
};
LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
// int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
// 进行函数动态注册
return env->RegisterNatives(clazz, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}
Java层实现
声明对应的native函数
public native int add(int a, int b);
public native String stringFromJNI();
3.2CMake构建脚本文件编写
CMake是一个跨平台的构建系统,会在接下来详细介绍它的语法及使用。
在项目cpp文件夹中新建一个CMakeLists.txt文件,AS新建的时候会自动为大家新建一个,平时JNI开发会又子目录,可能需要新建很多CMakeLists.txt文件。
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
#指定最小版本
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#IF (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
# ADD_DEFINITIONS(-DWindows)
#ELSE (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux")
# ADD_DEFINITIONS(-DLinux)
#ENDIF ()
# 搜索当前目录下的所有.cpp文件
aux_source_directory(. SRC_LIST)
#将SRC_LIST下的cpp文件生成名为native-lib2的动态库
add_library(native-lib2 SHARED ${SRC_LIST} MD5.cpp)
#FILE(GLOB SRC_LIST_SUB "${PROJECT_SOURCE_DIR}/src/main/cpp/cppp/*.cpp")
#FILE(GLOB SRC_LIST_SUB2 "${PROJECT_SOURCE_DIR}/src/main/cpp/*.cpp")
#add_library(native-lib SHARED ${SRC_LIST} ${SRC_LIST_SUB})
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
#查找指定的log库文件,并将路径存到log-lib中
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 指定动态链接库,native-lib2, jnigraphics,log-lib三个库
target_link_libraries( # Specifies the target library.
native-lib2
jnigraphics
# Links the target library to the log library
# included in the NDK.
${log-lib})
简单说明,后续会详细讲解:
指定最小版本
cmake_minimum_required(VERSION 3.4.1)
指定动态链接库,native-lib2, jnigraphics,log-lib三个库
target_link_libraries( # Specifies the target library.
native-lib2
jnigraphics
# Links the target library to the log library
# included in the NDK.
${log-lib})
查找指定的log库文件,并将路径存到log-lib中
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
搜索当前目录下的所有.cpp文件
aux_source_directory(. SRC_LIST)
将SRC_LIST下的cpp文件生成名为native-lib2的动态库
add_library(native-lib2 SHARED ${SRC_LIST} MD5.cpp)
4.gradle打包脚本编写
在根目录build.gradle文件中android里配置exteranlNativeBuild
android {
...
externalNativeBuild {
cmake {
cppFlags ""
// 配置构建相应的cpu架构平台so库
abiFilters 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
}
externalNativeBuild {
//指定CMakeLists.txt文件目录路径
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
5.通过CMake构建即可打出SO动态库文件
编译完成后会在项目以下(build/intermediates/cmake/debug/obj/对应的cpu架构平台/native-lib.so)文件中出现一个so文件。
../NativeApp/app/build/intermediates/cmake/debug/obj/armeabi-v7a
6.项目加载so库
引用编译好的so库,拷贝到jniLibs目录下,在项目使用之前需要loadLibrary动态库。说明下编译出来的是libnative-lib2名称,在System.loadLibrary中需要去掉lib。
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib2");
}
网友评论