JNI,是Java Native Interface的缩写,中文为Java本地调用。通俗地说,JNI是一种技术,通过这种技术可以做到以下两点:
· Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
· Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。
交叉编译
- 在一个平台下,编译出另一个平台能够执行的二进制代码
- 平台:windows , mac os, linux
- 处理器:x86(主要厂商英特尔和英伟达,pc一般用这个),arm(嵌入式设备,手机用这个),mips(开源的一个处理器架构,很多厂商对它进行修改)
原理:
- 源代码->编译->链接->可执行程序
- 模拟其他平台特性
交叉编译的工具链
- 多个工具集合,一个工具使用完后接着调用下一个工具
常见工具
- NDK:模拟其他平台特性来编译代码的工具
- CDT:C/C++ Development Tools(高亮显示C语言关键字)
- cygwin:模拟器,可以在windows下运行linux指令
NDK介绍
- NDK目录结构
- build/tools:linux的批处理文件
- platforms:编译C代码需要使用的头文件和类库
- prebuild:预编译使用的二进制可执行文件
- python-pachages:
- sources:NDK的源码
- toolchains:工具链
- ndk-build.cmd:编译打包C代码的一个指令,肯定会调用toolchains开发人员不用管
使用JNI
- 在项目根目录下创建 jni文件夹
- 在jni文件夹中创建一个 C 文件
- 除了两个标准头文件在包含
<jni.h>
- 在Java代码中创建一个本地方法helloFromC
publicnativeString helloFromC();
- 在JNI中定义函数实现这个方法,函数必须这么写
Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv*env,jobject obj)
- 其中Java是必须的关键字,后面跟着包名类名方法名,中间用_隔开
- 参数必须是JNIEnv* env和jobject obj
env是一个二级指针,指向存放Java虚拟机的内存地址的内存块 - obj表示那个对象调用该方法
- 可以使用javah指令自动生成:javah 包名.类名
- JDK1.7 在src目录下执行
- JDK1.6 在bin/classes目录下执行
- 结果:
jstring JNICALL Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv *, jobject);
- 结果:
- JDK1.6 在bin/classes目录下执行
返回一个字符串,用C定义一个字符串
char* cstr = "hello from C";
将C字符串转化成Java字符串
jstring jstr = (*env)->NewStringUTF(env,cstr);
- NewStringUTF是env所指向的指针所指向的结构体中的函数指针变量
在jni文件夹中创建Android.mk文件,文件内容如下
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) #编译生成的类库叫什么名字 LOCAL_MODULE:= hello #要编译的C文件 LOCAL_SRC_FILES:= Hello.c include $(BUILD_SHARED_LIBRARY)
- 在jni文件夹下执行ndk-build.cmd指令(要提前配置到环境变量中)
Java代码中加载so类库,调用本地方法
System.loadLibrary("hello");//一般在静态代码块中执行
注意:ndk-build.cmd指令执行默认生成arm架构的类库,如果需要支持其他cpu架构需要在jni文件夹下创建Application.mk文件里面加入需要支持的架构,如,加入x86的支持
APP_ABI :=armeabi armeabi-v7a x86
如果需要支持cpu全部架构,把等号右边换成all
JNI常见错误
- findLibrary returned null (类库加载失败)
- CPU平台不匹配
- 加载类库时,写错类库名字
- 本地方法找不到
- 忘记加载类库
- C代码中的和Java本地方法对应的方法名写错
Eclipse配置NDK开发环境
- 指定NDK位置:Windows->Preferences->Android->NDK->Broawse到NDK所放位置下的build文件夹
- 关联jni.h:选中项目右键->Properties->C/C++ General->Paths and Symbols->Add->选择NDK目录下的platforms选择对应的SDK版本号,选择cpu架构选择usr选择include(例如:D:\android\android-ndk-r11c\platforms\android-21\arch-arm\usr\include)->点击完成
配置完成后可以直接运行Android Application,在编译的时候会自动生成类库
利用逆向助手反编译apk
- 工具:Android逆向助手-v2.0.rar
- 提取Java代码
- 提取dex
- dex转jar
- 获取资源文件,图片,jar,so
- 解压缩即可
- 提取Java代码
C语言使用Logcat(调试)
- Android.mk文件增加
LOCAL_LDLIBS += -llog
C代码中增加
#include <android/log.h> #define LOG_TAG "hvcker" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
- 在代码中就可以用了
LOGI(“info\n”);
LOGD(“debug\n”);
- 在代码中就可以用了
在C中使用反射调用Java方法
/**
* 得到Java字节码对象内存地址
*/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass clazz = (*env)->FindClass(env,"com/example/jniccj/MainActivity");
/**
* 得到Java方法,最后一个参数是方法签名,可用java -s 获取
*/
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID methodID = (*env)->GetMethodID(env,clazz,"show","(Ljava/lang/String;)V");
/**
* 调用方法,CallXxxMethod,Xxx为方法返回值,最后一个参数是可变参数,传入方法参数
*/
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,methodID,(*env)->NewStringUTF(env,"hello Java"));
* javap -s 要在projectName/bin/classes目录下执行
* 执行代码:javap -s packageName.classnName
调用C++代码
#include <jni.h>
//包含同一个文件夹下的声明函数的头文件
#include "com_example_jnicpp_MainActivity.h"
JNIEXPORT jstring JNICALL Java_com_example_jnicpp_MainActivity_helloCpp(
JNIEnv * env, jobject obj) {
char * cstr = "hello from cpp";
//C++的env只是一个一级指针
return env->NewStringUTF(cstr);
}
- 包含头文件(用javah 生成的那个在src目录下的文件)
- C的env和C++的env的区别
- 在C中JNIEnv是一个结构体指针
typedef const struct JNINativeInterface* JNIEnv;
,后面再加一个*就表示是一个二级指针,所以用的时候要先取出env所指向的地址,改地址是指向JNINativeInterface结构体的指针,所以要调用JNINativeInterface里面的方法要(*env)->xxx- 在C++中JNIEnv是一个里面含有
JNINativeInterface
的结构体
typedef _JNIEnv JNIEnv;
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
...
}
所以C++的 JNIEnv* 是一个一级指针,所以调用的时候直接env->xxx就可以了。还有一点要注意的是:C++是面向对象的,所以它在执行类似newStringUTF方法时不需要传入本身env,直接env->newStringUTF(“hello from c”);底层也是调用
JNINativeInterface
中的函数
jstring NewStringUTF(const char* bytes)
{ return functions->NewStringUTF(this, bytes); }
分支C进程
int pid = fork();
//如果pid = 0分支成功
if(pid == 0){
while(1){
LOGD("fork C");
sleep(1);
}
}
- 分支出来的C进程在不同型号的手机会有不同的表现形式,例如在魅族手机,依赖于Android进程,程序管理里面强制停止,C进程随之停止,但在有些型号手机中,C进程怎么杀也杀不死,只能用adb shell kill [pid]来删除
- 可以分支出两个守护进程,这样怎么杀也杀不掉(可以给线程做保活)
常用代码(C部分)
中文乱码
jstring ctojstring(JNIEnv *env, char* tmpstr) { jclass Class_string; jmethodID mid_String, mid_getBytes; jbyteArray bytes; jbyte* log_utf8; jstring codetype, jstr; Class_string = (*env)->FindClass(env, "java/lang/String"); //获取class //先将gbk字符串转为java里的string格式 mid_String = (*env)->GetMethodID(env, Class_string, "<init>", "([BLjava/lang/String;)V"); bytes = (*env)->NewByteArray(env, strlen(tmpstr)); (*env)->SetByteArrayRegion(env, bytes, 0, strlen(tmpstr), (jbyte*) tmpstr); codetype = (*env)->NewStringUTF(env, "gbk"); jstr = (jstring) (*env)->NewObject(env, Class_string, mid_String, bytes,codetype); (*env)->DeleteLocalRef(env, bytes); //再将string变utf-8字符串。 mid_getBytes = (*env)->GetMethodID(env, Class_string, "getBytes", "(Ljava/lang/String;)[B"); codetype = (*env)->NewStringUTF(env, "utf-8"); bytes = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid_getBytes, codetype); log_utf8 = (*env)->GetByteArrayElements(env, bytes, JNI_FALSE); return (*env)->NewStringUTF(env, log_utf8); }
Java字符串转C
char* _JString2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env,"GB2312"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if(alen > 0) { rtn = (char*)malloc(alen+1); //"\0" memcpy(rtn, ba, alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env, barr, ba,0); return rtn; }
C语言操作Java数组
//拿到整形数据的长度和整形数组的指针 //jsize (*GetArrayLength)(JNIEnv*, jarray); int length = (*env)->GetArrayLength(env,jintarr); //jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); int * arrp = (*env)->GetIntArrayElements(env,jintarr,0); int i; for(i = 0;i<length;i++){ *(arrp+i)+=20; }