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)); // 8
printf("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");
}
@Override
protected 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 -lm
include $(BUILD_STATIC_LIBRARY) // 生成的是.so库
APP_ABI := all // 编译支持cpu的so库, all表示全编译, 多平台用空格隔开
APP_PLATFORM := android-16 // 编译版本(最小)
Java 与 C 之间的相互调用
Log
CMakeLists.txt添加依赖库
find_library(
log-lib
log )
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();
@Override
protected 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 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
*/
// 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: ()V
public native void callbackHelloFromJava();
descriptor: ()V
public native void add();
descriptor: ()V
public native void printString();
descriptor: ()V
public void helloFromJava();
descriptor: ()V
public int add(int, int);
descriptor: (II)I
public void printString(java.lang.String);
descriptor: (Ljava/lang/String;)V
static {};
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();
@Override
protected 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 {
@Override
protected 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成功, 返回子进程pid
pid == 0: 子进程不能fork, 返回0
pid < 0: fork失败
adb shell ps
root 140 1 503608 27568 ffffffff 00000000 S zygote
u0_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辅进程pid
root 1 0 1380 672 c0119ff0 00061e78 S /init
u0_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 0
am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com
execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://luzhuo.me/blog", (char *) NULL);
// 打开Activity
am start --user 0 -n me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivity
execlp("am", "am", "start", "--user","0", "-n" , "me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivity",(char *) NULL);
// 打开Service
am startservice --user 0 -n me.luzhuo.ndkdemome/me.luzhuo.ndkdemo.activity.MainService
*/
ppid = getppid(); // 获取父进程id
FILE* 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{
// 应用已停止运行, 打开Activity
execlp("am", "am", "start", "--user","0", "-n" , "me.luzhuo.ndkdemo/me.luzhuo.ndkdemo.MainActivity",(char *) NULL);
}
}
}
}else{ } // 子进程创建失败
}
只需将start
换成start service
就能永久运行service了, 除非手机被重启, 当然这在Android5.0以下才有用.
更多的am相关命令参考命令相关文章.