Android JNI/NDK
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相关推荐
- 【androidjniNDk】详细介绍每一步,让你轻松掌握android JNI NDk
一,开发环境配置 众所周知,万事开头难,配置开发环境是所有开发项目最基本且必须要做的一步. 所用的所有软件及开发平台:Win7+NetBeans+Vmware+ubun ...
- android jni ndk 视频分享
链接如下:http://download.csdn.net/detail/jltxgcy/5667327. 转载于:https://www.cnblogs.com/jiangu66/p/3162805 ...
- jni ndk_带有NDK的Android JNI应用程序
jni ndk In this tutorial, we'll be discussing JNI and develop a basic Android Application using NDK ...
- JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之jobject和jclass Android JNI/NDK入门指南目录 JNI/NDK入门指南之正确姿势了解JNI和NDK JNI/NDK入门指南之JavaVM和 ...
- JNI编程基础(二)——Android Studio NDK开发
由于笔者目前的工作是Android开发,所以在JNI开发系列博客中穿插一篇AndroidStudio NDK开发介绍. 随着Android Studio 2.2的推出,Android Studio的N ...
- Android JNI 和 NDK
1.Android NDK 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...
- Android 高级开发 JNI NDK 介绍与使用
Android 高级开发 JNI & NDK 介绍与使用 前言 对于没接触过的领域,即是挑战也是机遇,不仅能够提升自己的能力.还能够学习到新的技术知识 而学习新的技术的时候,最好是从头开始按照 ...
- Android Studio NDK环境配置及JNI使用方法
1.前言 什么是NDK? NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.N ...
- 5.NDK Android jni开发 异常处理 native奔溃解决(相机图片美化)
http://www.droidsec.cn/常见android-native崩溃及错误原因/ https://blog.csdn.net/ddxxii/article/details/8478111 ...
最新文章
- 人工智能与区块链交换了眼神儿,之后呢……
- ckplayer php,ckplayer 网页视频播放插件
- group by的查询
- SAP FI 会计凭证过账bapi BAPI_ACC_DOCUMENT_POST
- hdu 1115(多边形重心)
- SQL Server 数据库优化
- 亚洲综合竞争力排名发布:韩国位居第1,中国第9,大家怎么看?
- 信息学奥赛一本通(1164:digit函数)
- 'pip' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
- jenkins JDK的集成
- paypal订阅流程及api请求
- 趣味证书制作生成微信小程序源码
- mysql 求订单总金额,MySQL查询 每年 每月 每日 订单数和订单金额
- 仿宋小二在html中怎么设置,CSS 网页中正确设置字体的方法 - 文章教程
- java计算机毕业设计ssm基于SSM学生信息管理系统37myx(附源码、数据库)
- TCP/IP 详解(第 2 版) 笔记 / 3 链路层 / 3.2 以太网与 IEEE 802 LAN/MAN 标准 / 3.2.2 以太网帧格式
- ShardingSphere实践(1)——ShardingSphere介绍
- 主流配置中心的比较 Spring Cloud Config、Apollo、Nacos
- 神操作!树莓派上运行滴滴开源的Logi-KafkaManager,你见过没?
- 关于linux中DBG调试宏的使用总结,减少代码冗余