JNI


本文由 Luzhuo 编写,转发请保留该信息.
原文: https://blog.csdn.net/Rozol/article/details/88322757


AndroidStudio3.2
NDK(native develop kit): Google提供的做JNI的一套工具包.

JNI

  • Java native interface: Java本地接口, 使Java与C/C++可以相互调用.
  • 能够扩展Java的能力, 使其能够调用驱动
  • 由于C/C++高运行效率的特性, 优化耗时操作
  • C/C++的反编译难度比Java高
  • C/C++有大量的优秀的开源库

C 的最最最基础

编译步骤

1.搭建开发环境

DevCpp

下载并安装: https://pc.qq.com/detail/16/detail_163136.html

2.编写HelloWorld

// 导入包 .h:头文件
#include<stdio.h>  // io 标准输入
#include<stdlib.h>  // lib 标准函数库main(){  // main函数printf("HelloWorld!\n");system("pause"); // system执行win的pause命令
}

3.编译运行

按F11: 编译 + 运行

基本数据类型

Java数据类型 Java字节数 C数据类型 C字节数 description
boolean 1 - - 0为False, 非0为True
byte 1 - - -
char 2 char 1 不同
short 2 short 2 -
int 4 int 4 -
long 8 long 4 不同
float 4 float 4 -
double 8 double 8 -
- - signed - 有符号(默认), 用来修饰整形变量(char int short long)
- - unsigned - 无符号, 同上
- - void - 无值, 无定向指针
unsigned char c = 128;

输出函数

占位符 description
%d int
%ld long int
%lld long long
%hd short
%c char
%f float
%lf double
%u 无符号数
%x 十六进制输出int / long int / short int
%o 八进制输出
%s 字符串
int len = 10;
printf("number: %d\n", len); double d = 3.1415926;
printf("bumber: %.7lf\n", d);  // .7是控制小数点后的位数.printf("%#x\n", len);  // # 自动加前缀char array[] = {'a', 'b', 'c', 'd', '\0'};  // \0 为结束符 , 占5字节
char array2[] = "Hello 世界!";  // 字符串
printf("String: %s\n", array);
printf("String: %s\n", array2);

输入函数

int number;
scanf("%d", &number);  // & 取地址符 char string[4];  // 注: c的数组不检测小标越界 (可输入3字节, \0 占1字节)
scanf("%s", &string);
printf("输入的数字是: %d ;输入的字符串是: %s", number, string);

指针

swap(int* p1, int* p2){int temp = *p1;*p1 = *p2;*p2 = temp;
} main(){int i = 123;printf("i的内存地址: %#x\n", i); int* pointer = &i;  // int* int类型(指针类型与变量类型要匹配)的指针变量 (指针变量只能保存地址); & 取地址符  int i2 = *pointer;  // 把指针变量里的值取出来printf("读指针值: %d\n", i2); *pointer = 456;  // 修改指针变量里的值 printf("写指针值: %d\n", i); // 方法中的 值传递 和 引用传递 修改效果同Java (值传递数据不可被修改, 引用传递数据可被修改) int i3 = 123;int j3 = 456; swap(&i3, &j3);printf("i: %d ;j: %d\n", i3, j3); // 系统不允许存在野指针 (以下写法是错误的)// int* p3;// *p3 = 123; // 以下写法是正确的 int* p3 = 123;char* p4 = "hello!";// 一个地址变量占的空间int* p5;double* p6;// 64位系统输出为8字节, 64位系统64位系统总线,支持内存2^64字节, 8*8bit=64bit刚好够用 // 32位系统输出为4字节, 32位系统为32位系统总线,支持内存2^32=4GB, 4*8bit=32bit刚好够用 printf("int类型指针变量占%d个字节\n", sizeof(p5));  // 8printf("double类型指针变量占%d个字节\n", sizeof(p6));  // 8// 多级指针int i7 = 123;int* p7 = &i7;  // 一级指针int** p72 = &p7;  // 二级指针, 只能保存一级指针地址int*** p73 = &p72;  // 三级指针, 只能保存二级指针地址 // ...printf("三级指针取值%d\n", ***p73);  // 取值 printf("一级指针的地址%#x\n", **p73);  // 取地址 // 栈内存, 静态内存分配(大小固定, 地址连续), 系统统一分配统一回收, 并且是无法控制的 (函数调用完自动释放) int array8 = {1, 2, 3, 4};  // 栈内存 // 堆内存, 动态内存分配, 由自己控制分配和回收 int* p8 = malloc(sizeof(int)*8);  // 申请堆内存 p8 = realloc(p8, sizeof(int)*8 + 1);  // 重新申请堆内存 (空间足够则后续, 不足则寻找新空间, 旧元素会被自动转移)  free(p8);  // 回收堆内存
}

结构体 / 联合体 / 枚举 / 自定义类型

结构体

void show(){printf("hello.\n");
}
int show2(int i){return i*2;
}
struct Person{char sex;int age;void(*funcp)();  // 函数指针 int(*funcp2)(int);  // 定义: 返回值(*函数指针变量名)(返回值);
}; main(){// 结构体 (类似于Java中的Class) // 结构体大小 >= 结构体中每个变量占字节数总和, 并且大小是最大那个变量占字节数的整数倍 struct Person p9 = {'f', 10};printf("age: %d\n", p9.age);  // 取值 // 调用函数 p9.funcp = &show;  // 赋予函数指针 p9.funcp();p9.funcp2 = &show2;printf("show: %d\n", p9.funcp2(5));// 结构体多级指针struct Person* p9p = &p9;printf("*Person.age: %d", (*p9p).age);(*p9p).age = 21;// 间接应用运算符printf("Person(p9p) -> age: %d", p9p->age);
}

联合体

union Peron1{char sex;int age;
};main(){// 联合体, 多个变量占用同一块内存, 起到节省内存的作用 // 联合体占字节数为 其中成员占内存最大的一个 union Peron1 p10; p10.sex = "g";p10.age = 123;printf("union p10.sex: %c\n", p10.sex); // union p10.sex: {}

枚举

enum Color{RED, BLUE, YELLOW
}; main(){// 枚举, 从0开始, 规定了取值只能从枚举里取值 enum Color c = BLUE;printf("color: %d\n", c); // color: 1
}

自定义类型

typedef int I;typedef struct Person{char sex;int age;
} Per; main(){I i = 123;printf("I: %d\n", i);Per p11 = {"f", 21}; printf("Per: %d\n", p11.age);
}

JNI

交叉编译

交叉编译: 在一个平台上编译, 在另一平台执行的本地代码
原理: 模拟不同平台的特性去编译代码

  • cpu平台:

    • arm (90%+)
    • x86 (Intel)
    • mips (嵌入设备)
  • 操作系统平台:
    • windows
    • linux
    • mac
    • os

开发流程

1.下载NDK

SDK Manager -> Android SDK -> SDK Tools
选择以下工具进行下载

CMake // 构建工具
LLDB // 调试程序
NDK // jni工具包

2.创建项目

创建项目的第一页把Include C++ support√上.

比平时开发多了Customize C++ Support向导页:

  • C++ Standard: C++ 标准(默认就好), 默认Toolchain Default(使用CMake设置)
  • Exceptions Support: 启用对 C++ 异常处理的支持(√上)
  • Runtime Type Information Support: 启用RTTI(运行时类型信息)(√上)

创建完成后, 还会帮我们生成示范代码

  • cpp: C/C++放在cpp下
  • External Build Files: 构建脚本

CMakeLists.txt应用构建脚本

# 最低构建本机所需库
cmake_minimum_required(VERSION 3.4.1)# 可以定义多个library库
add_library(# 设置库的名称native-lib# 设置为共享库SHARED# 源文件相对路径src/main/cpp/native-lib.cpp )# 搜索预先构建的库
find_library( # 设置path变量名log-lib# 指定NDK库名log )# 指定库CMake应该链接到目标库, 可链接多个库
target_link_libraries( # 指定目标库native-lib# 目标库到log库的链接导入到NDK${log-lib} )

示范代码是C++, 如果换成C也是支持的:

#include <jni.h>jstring Java_me_luzhuo_hellondk_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){char* cstr = "Hello from C!";return (*env)->NewStringUTF(env, cstr);
}

2.1在原项目引进NDK

这是个小插曲, 如果你的项目已经开发, 并且还没有引入NDK, 那么这步适合你, 否则请跳过.

a.编写Native代码:

public class MainActivity extends AppCompatActivity {// 导入so库static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 调用本地方法TextView tv = (TextView) findViewById(R.id.sample_text);tv.setText(stringFromJNI());}// 定义本地方法public native String stringFromJNI();
}

b.编写C/C++

app/src/main下创建cpp文件夹

然后编写c代码.

#include <jni.h>// Java_包名_类名_本地方法名
// jobject thiz: 调用本地函数的Java对象(MainActivity)
// JNIEnv* env: 定义在jni.h文件里, 是结构体JNINativeInterface的二级指针, 所以JNIEnv是它的一级指针, JNINativeInterface中定义了常用的大量指针
jstring Java_me_luzhuo_hellondk_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){char* cstr = "Hello from C!";// 将char*转成jstring// 转换函数都在sdk\ndk-bundle\sysroot\usr\include\jni.h中的JNINativeInterface定义了,// 我们找到将char*转成jstring的函数 jstring (*NewStringUTF)(JNIEnv*, const char*);return (*env)->NewStringUTF(env, cstr);
}

以上的方法编写为了避免手动敲错, 可以生成头文件, 然后直接剪切粘贴:

生成.h头文件

cd app/src/main/java
javah me.luzhuo.hellondk.MainActivity

注: 我用的是java7

  • java7 在src运行javah
  • java6 在bin运行javah

生成的头文件并没有什么实际作用, 主要用于避免出错

打开me_luzhuo_hellondk_MainActivity.h头文件, copy相关方法代码到cpp下的相关.c文件里(如native-lib.c)

JNIEXPORT jstring JNICALL Java_me_luzhuo_hellondk_MainActivity_stringFromJNI(JNIEnv *, jobject);

JNIEXPORT 和 JNICALL 这些关键字可删可不删, 用完的头文件可以直接删了, 加上形参就可编写c代码了

c.在app下创建CMakeLists.txt文件

内容直接拷贝上面2的CMakeLists.txt内容即可, 内容都是一样的, 这里略

d.build.gradle(app)配置

android标签内配置

android {...externalNativeBuild {cmake {path "CMakeLists.txt"}}
}

e.然后Build -> Make Project, 会自动在build\intermediates\cmake\debug\obj生成对应的.so库

注意:

以下这段配置是不需要配置的, 如果你点的是Build -> Make Project构建会帮你构建全部cpu的so库, 如果你点运行构建, 只会帮你构建你运行设备的cpu动态链接库 (如 运行于联想S658t手机只会帮你编译armeabi-v7a这一个so库, 运行于华为Note8只会帮你编译arm64-v8a这一个so库)

ndk {abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}

旧版本的一些小知识

Android.mk // 告诉系统资源在哪

LOCAL_PATH := $(call my-dir)  // 当前目录include $(CLEAR_VARS)  // 清除变量LOCAL_MODULE    := lua  // 生成文件的名字
LOCAL_SRC_FILES := lapi.c lauxlib.c  // c的源文件, 多个用空格隔开
LOCAL_LDLIBS    := -ld -lminclude $(BUILD_STATIC_LIBRARY)  // 生成的是.so库

Application.mk

APP_ABI := all  // 编译支持cpu的so库, all表示全编译, 多平台用空格隔开
APP_PLATFORM := android-16  // 编译版本(最小)

Java 与 C 之间的相互调用

Log

CMakeLists.txt添加依赖库

find_library( log-liblog )target_link_libraries(// ...${log-lib} )

在C中的使用:

// log
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)JNIEXPORT jintArray JNICALL Java_me_luzhuo_ndkdemo_JavaCallCUtils_passArray(JNIEnv * env, jobject thiz, jintArray intArray){jsize length = (*env)->GetArrayLength(env, intArray);LOGD("intArray长度: %d", length);return intArray;

Java调用C

Java与C的关系见 sdk\ndk-bundle\sysroot\usr\include\jni.h 文件定义.

C中的数据类型与Java中数据类型的关系

typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;

本地代码定义:

public class JavaCallCUtils {static {System.loadLibrary("jni-lib");}public native int passInt(int x, int y);public native String passString(String s);public native int[] passArray(int[] ints);
}

调用本地方法

public class JavaCallCActivity extends AppCompatActivity {private static final String TAG = JavaCallCActivity.class.getSimpleName();JavaCallCUtils jni = new JavaCallCUtils();@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_javacallc);}/*** 传递基本数据类型*/public void ints(View view) {Log.e(TAG, "" + jni.passInt(5, 6));}/*** 传递Java特有数据类型*/public void strings(View view) {Log.e(TAG, "" + jni.passString("World"));}/*** 传递数组*/public void arrays(View view) {Log.e(TAG, "" + Arrays.toString(jni.passArray(new int[]{1, 2, 3})));}
}

调用的C代码:

#include <jni.h>
#include <stdlib.h>
#include <string.h>/** Class:     me_luzhuo_ndkdemo_JavaCallCUtils* Method:    passInt* Signature: (II)I*/
JNIEXPORT jint JNICALL Java_me_luzhuo_ndkdemo_JavaCallCUtils_passInt(JNIEnv * env, jobject thiz, jint intx, jint inty){/*typedef unsigned char   jboolean;       // unsigned 8 bitstypedef signed char     jbyte;          // signed 8 bitstypedef unsigned short  jchar;          // unsigned 16 bitstypedef short           jshort;         // signed 16 bitstypedef int             jint;           // signed 32 bitstypedef long long       jlong;          // signed 64 bitstypedef float           jfloat;         // 32-bit IEEE 754typedef double          jdouble;        // 64-bit IEEE 754*/// C的int和java的jint相同, 直接操作就行了return intx * inty;
}/** Class:     me_luzhuo_ndkdemo_JavaCallCUtils* Method:    passString* Signature: (Ljava/lang/String;)Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_me_luzhuo_ndkdemo_JavaCallCUtils_passString(JNIEnv * env, jobject thiz, jstring s){// typedef jobject         jstring;// 将Java的jstring转成C的char*类型// const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);char* cstr = (*env)->GetStringUTFChars(env, s, NULL);int length = strlen(cstr); // 获取字符串长度int i;for(i = 0; i < length; i++){*(cstr+i) += 1;}// 将C的char*类型转成Java的jstring// jstring     (*NewStringUTF)(JNIEnv*, const char*);return (*env)->NewStringUTF(env, cstr);
}/** Class:     me_luzhuo_ndkdemo_JavaCallCUtils* Method:    passArray* Signature: ([I)[I*/
JNIEXPORT jintArray JNICALL Java_me_luzhuo_ndkdemo_JavaCallCUtils_passArray(JNIEnv * env, jobject thiz, jintArray intArray){// jsize       (*GetArrayLength)(JNIEnv*, jarray);jsize length = (*env)->GetArrayLength(env, intArray);// jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);int* arrayPointer = (*env)->GetIntArrayElements(env, intArray, NULL);int i;for(i = 0; i < length; i++){*(arrayPointer+i) += 10;}return intArray;
}

JNI的jstring类型和C的Char*类型相互转换的方法:

// 获取字符串长度
jsize       (*GetStringLength)(JNIEnv*, jstring);
jsize       (*GetStringUTFLength)(JNIEnv*, jstring);// char* -> jstring
jstring     (*NewString)(JNIEnv*, const jchar*, jsize);
jstring     (*NewStringUTF)(JNIEnv*, const char*);// jstring -> char*
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);  // (utf-16)
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);  // (utf-8)// jstring -copy-> jchar* (参数: jstring源 / start / len / jchar*目标)
void        (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*);
void        (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*);// 释放指针
void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
void        (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);

基本类型数组的方法

// array -> array*
jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, jint mode)
void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, jint mode)
void ReleaseCharArrayElements(jcharArray array, jchar* elems, jint mode)
void ReleaseShortArrayElements(jshortArray array, jshort* elems, jint mode)
void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode)
void ReleaseLongArrayElements(jlongArray array, jlong* elems, jint mode)
void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, jint mode)
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, jint mode)// array -copy-> array*
void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean* buf)
void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte* buf)
void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar* buf)
void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort* buf)
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint* buf)
void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong* buf)
void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat* buf)
void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble* buf)void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf)
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf)jbooleanArray NewBooleanArray(jsize length)
jbyteArray NewByteArray(jsize length)
jcharArray NewCharArray(jsize length)
jshortArray NewShortArray(jsize length)
jintArray NewIntArray(jsize length)
jlongArray NewLongArray(jsize length)
jfloatArray NewFloatArray(jsize length)
jdoubleArray NewDoubleArray(jsize length)

C调用Java

获取方法签名:

cd app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes
javap -s me.luzhuo.ndkdemo.JNIDemoClass

生成内容如下

Compiled from "JNIDemoClass.java"
public class me.luzhuo.ndkdemo.JNIDemoClass {public me.luzhuo.ndkdemo.JNIDemoClass();descriptor: ()Vpublic native void callbackHelloFromJava();descriptor: ()Vpublic native void add();descriptor: ()Vpublic native void printString();descriptor: ()Vpublic void helloFromJava();descriptor: ()Vpublic int add(int, int);descriptor: (II)Ipublic void printString(java.lang.String);descriptor: (Ljava/lang/String;)Vstatic {};descriptor: ()V
}

比如: ()V就是helloFromJava()方法的方法签名

C里面调用Java方法的步骤:

Java代码:

package me.luzhuo.ndkdemo;public class JNIDemoClass {static {System.loadLibrary("jni-lib");}public native void callbackHelloFromJava();public void helloFromJava(){Log.e(TAG, "" + "helloFromJava");}
}

C代码:

JNIEXPORT void JNICALL Java_me_luzhuo_ndkdemo_JNIDemoClass_callbackHelloFromJava(JNIEnv * env, jobject thiz){// 1.获取字节码对象// jclass      (*FindClass)(JNIEnv*, const char*);jclass clazz = (*env)->FindClass(env, "me/luzhuo/ndkdemo/JNIDemoClass");// 2.获取Method对象// jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)// { return functions->GetMethodID(this, clazz, name, sig); }jmethodID methodid = (*env)->GetMethodID(env, clazz, "helloFromJava", "()V");// 3.字节码对象创建Object// 这里的object是thiz, 因为是通过Java调用C代码传进来的实例对象// 4.通过对象调用方法(*env)->CallVoidMethod(env, thiz, methodid);
}

1.获取字节码对象: 类名为全类名

// jclass      (*FindClass)(JNIEnv*, const char*);
jclass clazz = (*env)->FindClass(env, "me/luzhuo/ndkdemo/JNIDemoClass");

2.获取Method对象:

// jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
jmethodID methodid = (*env)->GetMethodID(env, clazz, "helloFromJava", "()V");

helloFromJava是Java中定义的方法名
()V是方法签名, 获取方式上面有

获取方法

// 参数: clazz / 方法名称 / 方法签名
jmethodID   GetMethodID(jclass clazz, const char* name, const char* sig)
jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);

当然还能获取字段

// 参数: clazz / 字段名称 / 字段签名
jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jfieldID    (*GetStaticFieldID)(JNIEnv*, jclass, const char*, const char*);

3.字节码对象创建Object
由上面的java代码可知, 我把 native方法 和 被c调用的方法 都写在JNIDemoClass类中, 所以这里的objectobject就是thiz, 也是JNIDemoClass实例对象, 这是通过Java调用C代码传进来的实例对象.

如果不是写在同一类中怎么办, 可使用以下代码创建

// 3.字节码对象创建Object
// jobject     (*AllocObject)(JNIEnv*, jclass);
jobject obj = (*env)->AllocObject(env, clazz);

创建对象的方法

jobject     (*AllocObject)(JNIEnv*, jclass);
jobject     (*NewObject)(JNIEnv*, jclass, jmethodID, ...);

插播: 关于NewObject的案例

JNIEXPORT void JNICALL Java_me_luzhuo_ndkdemo_JNIDemoClass_time(JNIEnv * env, jobject thiz){// 1.获取字节码对象jclass clazz_date = (*env)->FindClass(env, "java/util/Date");// 2.获取Method对象jmethodID method_date_getTime = (*env)->GetMethodID(env, clazz_date, "getTime", "()V");// 3.字节码对象创建Object// 构造方法的方法名为<init>jmethodID method_date = (*env)->GetMethodID(env, clazz_date, "<init>", "()V");jobject obj_date = (*env)->NewObject(clazz_date, method_date);// 4.通过对象调用方法jlong time = (*env)->CallLongMethod(env, obj_date, method_date_getTime);
}

4.通过对象调用方法

// 4.通过对象调用方法
(*env)->CallVoidMethod(env, thiz, methodid);

由于java方法public void helloFromJava();是返回Void类型的, 所以选择CallVoidMethod函数, 如果是返回其他类型的, 还有以下可以选择

jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);jobject     (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
jboolean    (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);
jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
jchar       (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);
jshort      (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);
jint        (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);
jlong       (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);
jfloat      (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...) __NDK_FPABI__;
jdouble     (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...) __NDK_FPABI__;
void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);// 调用父类方法, 参数都要传父类的(father_obj / father_clazz / father_methodid)
jobject     (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jboolean    (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jbyte       (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jchar       (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jshort      (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jint        (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jlong       (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);
jfloat      (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, jmethodID, ...) __NDK_FPABI__;
jdouble     (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, jmethodID, ...) __NDK_FPABI__;
void        (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, jmethodID, ...);

当然字段的使用也同方法类似

jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jboolean    (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
jbyte       (*GetByteField)(JNIEnv*, jobject, jfieldID);
jchar       (*GetCharField)(JNIEnv*, jobject, jfieldID);
jshort      (*GetShortField)(JNIEnv*, jobject, jfieldID);
jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);
jlong       (*GetLongField)(JNIEnv*, jobject, jfieldID);
jfloat      (*GetFloatField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;
jdouble     (*GetDoubleField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
void        (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
void        (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
void        (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
void        (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
void        (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
void        (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat) __NDK_FPABI__;
void        (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble) __NDK_FPABI__;jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
jboolean    (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);
jbyte       (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);
jchar       (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);
jshort      (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);
jint        (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);
jlong       (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);
jfloat      (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID) __NDK_FPABI__;
jdouble     (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID) __NDK_FPABI__;void        (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);
void        (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);
void        (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);
void        (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);
void        (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);
void        (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);
void        (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
void        (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat) __NDK_FPABI__;
void        (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble) __NDK_FPABI__;

需要上下文的方法:

比如说我想调用以下方法;

public void showToast(String s){Toast.makeText(getApplicationContext(), "" + s, Toast.LENGTH_SHORT).show();
}

这段代码需要getApplicationContext(), 这可不能直接new出来, 以下是解决方案:

public class JNIDemoClass {private Context mContext;public JNIDemoClass(Context context){this.mContext = context;}static {System.loadLibrary("jni-lib");}public native void toast();public void showToast(String s){Toast.makeText(mContext, "" + s, Toast.LENGTH_SHORT).show();}
}

就是在创建JNIDemoClass对象的时候, 直接把Context给他就好了.

c的代码都一样

JNIEXPORT void JNICALL Java_me_luzhuo_ndkdemo_JNIDemoClass_toast(JNIEnv * env, jobject thiz){// 1.获取字节码对象jclass clazz = (*env)->FindClass(env, "me/luzhuo/ndkdemo/JNIDemoClass");// 2.获取Method对象jmethodID methodid = (*env)->GetMethodID(env, clazz, "showToast", "(Ljava/lang/String;)V");// 3.字节码对象创建Object// jobject     (*AllocObject)(JNIEnv*, jclass);/* jobject obj = (*env)->AllocObject(env, clazz); */// 4.通过对象调用方法jstring str = (*env)->NewStringUTF(env, "这里是C");(*env)->CallVoidMethod(env, thiz/*obj*/, methodid, str);
}

Java的参数要String类型, 而C里只有char*类型, 需要转成String类型

jstring str = (*env)->NewStringUTF(env, "这里是C");

Java与C++的互调

上面写的都是C语言的, C++与C的用法差别很小.

1.编写Java代码

在java中写法都是一样的

package me.luzhuo.ndkdemo;public class JavaCallCppUtils {static {System.loadLibrary("jni-lib");}public native String passString(String s);
}public class JavaCallCppActivity extends AppCompatActivity {private static final String TAG = JavaCallCppActivity.class.getSimpleName();JavaCallCppUtils jni = new JavaCallCppUtils();@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_javacallc);}public void strings(View view) {Log.e(TAG, "" + jni.passString("World"));}
}

2.生成.h头文件

使用javah命令生成.h头文件, 在C的用法里头文件是可以删除的, 但是在C++的用法里, 这个头文件有用.

cd app/src/main/java
javah me.luzhuo.ndkdemo.JavaCallCppUtils

把头文件剪切到cpp文件夹

3.编写C++代码

在cpp文件夹里创建以.cpp为后缀的文件 (如 javacallcpp.cpp)

同样需要把.h头文件里生成的方法copy到.cpp文件里.

C++代码:

#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include "me_luzhuo_ndkdemo_JavaCallCppUtils.h"  // ""号先从本地找, 再去include找; <>号直接去include找/** Class:     me_luzhuo_ndkdemo_JavaCallCppUtils* Method:    passString* Signature: (Ljava/lang/String;)Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_me_luzhuo_ndkdemo_JavaCallCppUtils_passString(JNIEnv * env, jobject thiz, jstring s){// const char* GetStringUTFChars(jstring string, jboolean* isCopy)const char* cstr = env->GetStringUTFChars(s, NULL);int length = strlen(cstr); // 获取字符串长度char* ncstr = new char[length];strcpy(ncstr,cstr);// void ReleaseStringUTFChars(jstring string, const char* utf)env->ReleaseStringUTFChars(s, cstr);int i;for(i = 0; i < length; i++){*(ncstr+i) += 1;}// jstring NewStringUTF(const char* bytes)return env->NewStringUTF((const char*)ncstr);
}

C++函数要先声明再使用, 所以要把头文件include进来

C与C++的JNIEnv区别:

这是在jni.h里定义的JNIEnv.

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

C的JNIEnv是JNINativeInterface*, 是JNIEnv的二级指针, 所以用法是

// const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
char* cstr = (*env)->GetStringUTFChars(env, s, NULL);

C++的JNIEnv是_JNIEnv, 是_JNIEnv的一级指针, 实际还是去调用JNINativeInterface*里定义的方法, 并且传递了this(_JNIEnv对象), 所以我们调用时就不需要传env了,

struct _JNIEnv {/* do not rename this; it does not seem to be entirely opaque */const struct JNINativeInterface* functions;#if defined(__cplusplus)const char* GetStringUTFChars(jstring string, jboolean* isCopy){ return functions->GetStringUTFChars(this, string, isCopy); }

所以用法是

// const char* GetStringUTFChars(jstring string, jboolean* isCopy)
const char* cstr = env->GetStringUTFChars(s, NULL);

C/C++引用类型

  • 局部引用:

    • 最常见的应用类型, JNI返回的引用都是局部应用
    • 局部引用在该native函数中有效, 函数返回后自动释放, 也可使用DeleteLocalRef手动释放
    • 如果一个局部引用指向一个庞大的对象时, 最好使用完后调用DeleteLocalRef, 以确保触发垃圾回收器的时候能够回收
  • 全局引用:
    • 可以跨线程, 多个native都有效
    • 需要手动创建NewGlobalRef, 手动释放ReleaseGlobalRef
  • 弱引用:
    • 需要手动创建NewWeakGlobalRef, 手动释放ReleaseWeakGlobalRef
jobject     (*NewLocalRef)(JNIEnv*, jobject);
jobject     (*NewGlobalRef)(JNIEnv*, jobject);
jweak NewWeakGlobalRef(jobject obj)void        (*DeleteLocalRef)(JNIEnv*, jobject);
void        (*DeleteGlobalRef)(JNIEnv*, jobject);
void DeleteWeakGlobalRef(jweak obj)// 两引用是否相等
jboolean    (*IsSameObject)(JNIEnv*, jobject, jobject);

注意

  • 使用.so文件注意与它的全类名(包名+类名)要对应

    • 不要用了别人的so文件, 却把native代码却放到自己的包下, 这是不对的
  • native方法名和包名均不能被混淆
    • 混淆之后就找不到了
  • 被c调用的方法名也不能被混淆
  • jni调用在主线程进行, 耗时操作请用辅线程or辅进程

案例 (fork进程)

这里编写一个永远运行的Activity的方法案例.

这个案例能在Android5.0以下正常运行, 如果是5.0+的版本, 那就不行了.
因为Android5.0+系统回收策略改成了进程组, 并且应用被杀之后, 任何Service也会被杀.

相关命令

adb shell
ps // 查看进程
kill 28553  // 杀死某pid进程

Java代码

public class ForkUtils {static {System.loadLibrary("jni-lib");}public native void fork();
}public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 永久运行new ForkUtils().fork();}
}

C代码:

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)int ppid;
/** Class:     me_luzhuo_ndkdemo_jni_ForkUtils* Method:    fork* Signature: ()V*/
JNIEXPORT void JNICALL Java_me_luzhuo_ndkdemo_jni_ForkUtils_fork(JNIEnv * env, jobject thiz){int pid = fork();/*fork只能在主进程, 分叉出的子进程还会再执行一次此处代码, 子进程不由Android管理pid > 0: fork成功, 返回子进程pidpid == 0: 子进程不能fork, 返回0pid < 0: fork失败adb shell psroot      140   1     503608 27568 ffffffff 00000000 S zygoteu0_a121   28553 140   550756 26488 ffffffff 4005b624 S me.luzhuo.ndkdemo  // 主进程u0_a121   28863 28553 545644 13716 c006adc8 4005ad6c S me.luzhuo.ndkdemo  // 子进程, 26082本进程pid, 25784辅进程pidroot      1     0     1380   672   c0119ff0 00061e78 S /initu0_a121   28863 1     545644 13716 c006adc8 4005ad6c S me.luzhuo.ndkdemo  // kill 28553 后*/if(pid > 0){ // 主进程}else if(pid == 0){ // 子进程while(1){/*如果子进程不结束, 父进程被结束(即使被卸载), 那么父进程的pid变为1*/LOGD("sub proceess is running");sleep(2); // 2s/*am命令: (命令文件存于:system/bin/am)// 打开浏览器 / 设备16+支持多用户, 需要指定--user 0am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.comexeclp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://luzhuo.me/blog", (char *) NULL);// 打开Activityam start --user 0 -n me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivityexeclp("am", "am", "start", "--user","0", "-n" , "me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivity",(char *) NULL);// 打开Serviceam startservice --user 0 -n me.luzhuo.ndkdemome/me.luzhuo.ndkdemo.activity.MainService*/ppid = getppid(); // 获取父进程idFILE* file;if(ppid == 1){file = fopen("/data/data/me.luzhuo.ndkdemo", "r");if(file == NULL){// 应用已被卸载, 打开网页execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://luzhuo.me/blog", (char *) NULL);}else{// 应用已停止运行, 打开Activityexeclp("am", "am", "start", "--user","0", "-n" , "me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivity",(char *) NULL);}}}}else{ } // 子进程创建失败
}

只需将start换成start service就能永久运行service了, 除非手机被重启, 当然这在Android5.0以下才有用.

更多的am相关命令参考命令相关文章.

Android JNI/NDK相关推荐

  1. 【androidjniNDk】详细介绍每一步,让你轻松掌握android JNI NDk

    一,开发环境配置         众所周知,万事开头难,配置开发环境是所有开发项目最基本且必须要做的一步.         所用的所有软件及开发平台:Win7+NetBeans+Vmware+ubun ...

  2. android jni ndk 视频分享

    链接如下:http://download.csdn.net/detail/jltxgcy/5667327. 转载于:https://www.cnblogs.com/jiangu66/p/3162805 ...

  3. jni ndk_带有NDK的Android JNI应用程序

    jni ndk In this tutorial, we'll be discussing JNI and develop a basic Android Application using NDK ...

  4. JNI/NDK入门指南之jobject和jclass

          JNI/NDK入门指南之jobject和jclass Android JNI/NDK入门指南目录 JNI/NDK入门指南之正确姿势了解JNI和NDK JNI/NDK入门指南之JavaVM和 ...

  5. JNI编程基础(二)——Android Studio NDK开发

    由于笔者目前的工作是Android开发,所以在JNI开发系列博客中穿插一篇AndroidStudio NDK开发介绍. 随着Android Studio 2.2的推出,Android Studio的N ...

  6. Android JNI 和 NDK

    1.Android NDK 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...

  7. Android 高级开发 JNI NDK 介绍与使用

    Android 高级开发 JNI & NDK 介绍与使用 前言 对于没接触过的领域,即是挑战也是机遇,不仅能够提升自己的能力.还能够学习到新的技术知识 而学习新的技术的时候,最好是从头开始按照 ...

  8. Android Studio NDK环境配置及JNI使用方法

    1.前言 什么是NDK? NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.N ...

  9. 5.NDK Android jni开发 异常处理 native奔溃解决(相机图片美化)

    http://www.droidsec.cn/常见android-native崩溃及错误原因/ https://blog.csdn.net/ddxxii/article/details/8478111 ...

最新文章

  1. 人工智能与区块链交换了眼神儿,之后呢……
  2. ckplayer php,ckplayer 网页视频播放插件
  3. group by的查询
  4. SAP FI 会计凭证过账bapi BAPI_ACC_DOCUMENT_POST
  5. hdu 1115(多边形重心)
  6. SQL Server 数据库优化
  7. 亚洲综合竞争力排名发布:韩国位居第1,中国第9,大家怎么看?
  8. 信息学奥赛一本通(1164:digit函数)
  9. 'pip' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  10. jenkins JDK的集成
  11. paypal订阅流程及api请求
  12. 趣味证书制作生成微信小程序源码
  13. mysql 求订单总金额,MySQL查询 每年 每月 每日 订单数和订单金额
  14. 仿宋小二在html中怎么设置,CSS 网页中正确设置字体的方法 - 文章教程
  15. java计算机毕业设计ssm基于SSM学生信息管理系统37myx(附源码、数据库)
  16. TCP/IP 详解(第 2 版) 笔记 / 3 链路层 / 3.2 以太网与 IEEE 802 LAN/MAN 标准 / 3.2.2 以太网帧格式
  17. ShardingSphere实践(1)——ShardingSphere介绍
  18. 主流配置中心的比较 Spring Cloud Config、Apollo、Nacos
  19. 神操作!树莓派上运行滴滴开源的Logi-KafkaManager,你见过没?
  20. 关于linux中DBG调试宏的使用总结,减少代码冗余

热门文章

  1. pytorch1.10+cuda11.3+minconda+pycharm(win11)
  2. 易达项目的工作进程第三天
  3. 一个售后工程师的逆袭
  4. 牛客网前端面试题错题总结
  5. pandas相关系数矩阵
  6. knockout ififnot
  7. 【系统重装】取消U盘分区
  8. 【性能测试】压力测试指标全解之TP指标(50/90/99/999)
  9. mysql开启binlog步骤讲解
  10. 笔记本合上以后不关闭外接显示器