JAVA通过JNI与C语言通信
一、JAVA调用C中的方法传递字符串参数并返回字符串
由于c语言中没有String数据类型,当C语言的函数被java调用的传递字符串或返回字符串的时候必须要用C语言中的字符数组进行转换。
java字符串转C语言字符数组:
1. 获取字符串jstr的长度
jsize len = (*env)->GetStringLength(env,jstr);
2. 申请内存空间,并以char型的指针指向内存地址
char *cstr = (char *)malloc(len+1);//C语言中以'\0'作为字符数组的结束,所以长度要+1
3. 将传递过来的字符串转换并赋值给*cstr
//jstrjava字符串源
//0 要获取的字符串内容的开始位置
//len 要获取的长度
//cstr要赋值的内存地址
(*env)->GetStringUTFRegion(env,jstr,0,len,cstr);//将Java传递过来的字符串赋值给cstr指向的内存空间
4. 切记在函数结束前要释放内存空间
free(cstr);
cstr = NULL;
C语言数据转Java字符串
jstring jstr;//待返回的java字符串
char buf[] = "hello";//c语言数组
jstr = (*env)->NewStringUTF(env,buf);
C语言中调用android的日志打印函数
#include <android/log.h>//日志打印函数的头文件
#define TAG "System.out" //日志的 TAG标识 可以任意
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) //主要填充第三个参数(可变参数)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG, __VA_ARGS__)//主要填充第三个参数(可变参数)
//在需要打印的地方
LOGD("str = %s","hello");
//注意:在android中添加 连接的库文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := strtool
LOCAL_SRC_FILES := strtool.c
LOCAL_LDLIBS += -llog #指定链接使用liblog.so库文件
include $(BUILD_SHARED_LIBRARY)
C语言中调用android的日志打印函数
#include <android/log.h>//日志打印函数的头文件
#define TAG "System.out" //日志的 TAG标识 可以任意
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) //主要填充第三个参数(可变参数)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG, __VA_ARGS__)//主要填充第三个参数(可变参数)
//在需要打印的地方
LOGD("str = %s","hello");
//注意:在android中添加 连接的库文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := strtool
LOCAL_SRC_FILES := strtool.c
LOCAL_LDLIBS += -llog #指定链接使用liblog.so库文件
include $(BUILD_SHARED_LIBRARY)
二、传递数组
C语言接收java传递的int型数组,并对数组排序后返回,由于数组传递的都是数组的首地址,所以在C语言中可以直接用一个int型的指针变量指向数组的地址,即可对数组进行操作。
//获取数组的长度
jsize len = (*env)->GetArrayLength(env,jarray);
//定义int型指针指向int型数组的首元素地址
jint *carray = (*env)->GetIntArrayElements(env,jarray,NULL);//参数三告java内存回收器,不要回收arr数组的内存,或者不要整理
内存如果回收器,回收和整理内存 ,那么我们在c中,访问的地址就可能错了
C调用JAVA中的方法
本地方法C语言要调用java中的函数,需要如下三步骤:
1. 获得类的字节码文件对象
2. 获得方法的唯一签名,获得方法签名可以用javap -s 命令获得
3. 执行方法
获得java类中方法的签名
在bin classes 目录下 执行javap -s 包名.类名获得类中方法签名
本地方法C语言要调用java中的函数,需要如下三步骤:
1. 获得类的字节码文件对象
2. 获得方法的唯一签名,获得方法签名可以用javap -s 命令获得
3. 执行方法
获得java类中方法的签名
在bin classes 目录下 执行javap -s 包名.类名获得类中方法签名
MainActivity.java中代码如下:
public class MainActivity extends Activity {
private AlertDialog dialog;
private EditText et_name;
static{
System.loadLibrary("alipay");//加载动态库
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText)findViewById(R.id.et_name);
}
public void click(View v)
{
final String str = et_name.getText().toString();
if(TextUtils.isEmpty(str))
{
Toast.makeText(MainActivity.this, "不能为空",Toast.LENGTH_LONG).show();
return;
}
new Thread(){
public void run()
{
safepay(str);//调用C语言方法
}
}.start();
}
public native void safepay(String name);
public void showDialog(final String msg)//此方法会被C语言中方法调用
{
if(dialog != null)
{
dialog.dismiss();
}
runOnUiThread(new Runnable(){//
public void run()
{
AlertDialog.Builder build = new AlertDialog.Builder(MainActivity.this);
build.setTitle("提示");
build.setMessage(msg);
dialog = build.show();
}
});
}
public void dismiss()
{
dialog.dismiss();
}
}
本地C语言
#include <jni.h>
#include <stdlib.h>
//注意这里第三个参数,要用C语言中的字符指针来指向传递过来的字符串
void showDialog(JNIEnv* env, jobject obj, char *jstr)
{
//1.获得字节码文件对象
jclass clazz = (*env)->FindClass(env,"com/pgm/c/MainActivity");//类的全路径,一定注意中间以'/'分隔
//2.获取类中的方法签名
jmethodID methodId = (*env)->GetMethodID(env,clazz,"showDialog","(Ljava/lang/String;)V");
//3.执行方法 用方法返回值相对应的方法回调函数执行 比如showDialog 返回值为void 此处用 CallVoidMethod();
jstring params =(*env)->NewStringUTF(env,jstr);//java中 showDialog方法的参数
(*env)->CallVoidMethod(env,obj,methodId,params);
}
void dismiss(JNIEnv* env, jobject obj)
{
//1.获得字节码文件对象
jclass clazz = (*env)->FindClass(env,"com/pgm/c/MainActivity");//类的全路径
//2.获取类中的方法签名
jmethodID methodId = (*env)->GetMethodID(env,clazz,"dismiss","()V");
//3.执行方法 用方法返回值相对应的方法回调函数执行 比如showDialog 返回值为void 此处用 CallVoidMethod();
(*env)->CallVoidMethod(env,obj,methodId);
}
JNIEXPORT void JNICALL Java_com_pgm_c_MainActivity_safepay
(JNIEnv *env, jobject obj, jstring jstr)
{
jsize len = (*env)->GetArrayLength(env,jstr);//获得字符串的长度
char *cstr = (char *)malloc(len+1);//开辟内存空间存储 字符串
(*env)->GetStringUTFRegion(env,jstr,0,len,cstr);//存储字符串
showDialog(env,obj,cstr);
sleep(2);//休眠2秒
dismiss(env,obj);
}
【疑问】在上述代码中我必须要将safepay()本地方法放在子线程调用,然后将本地方法中要调用的java方法showDialog()声明在主线程运行,才会出正确的结果;而将safepay()直接放在主线程调用,程序没有任何反应也不会包任何错误。此处不堪甚解,看来JNI开发的多线程相互调用,还有待探索。
开发模式
- 只有一个android工程师,jni流程,本地c程序
- 只有一个android工程师,有.so库,头文件,文档
- 有android,有c工程师,把c工程师提供c函数包装调用
- 把c工程师提供.c和.h文件拷贝到工程的jni文件夹下
- 在jni源文件中包含.h头文件,调用基中c工程师实现的函数
- 在Android.mk文件中LOCALSRCFILES 需要指定编译c工程师提供.c源文件
JNI实现底层监控应用程序被卸载
基本原理: 在本地方法中对当前进程进行克隆,建立一个子进程,就算父进程被杀死或关闭,子进程在正常情况下一直会运行,除非用户关机。应用程序安装到手机上后都会在data/data目录下创建该应用程序的私有空间(文件夹),在应用程序被用户卸载后,系统会删除对应的文件夹,所以我们可以通过子进程来检查该文件夹的存在与否判断我们的引用是否被用户卸载,从而做出相应的后续操作。
本地C源代码
#include <jni.h>
#include <sys/types.h>
#include <unistd.h>//注意要添加此头文件 否则 fork() access()等方法无法无法调用
JNIEXPORT void JNICALL Java_com_example_trace_MainActivity_createChildProgress
(JNIEnv *env, jobject obj)
{
int pid = fork();//创建子进程
if(pid < 0)
{
exit(-1);//直接退出
}else if(pid == 0)//子进程满足条件
{
while(1)
{
if(access("/data/data/com.example.trace",F_OK) < 0)//检查文件是否存在
{ //弹出网页 实际是想虚拟机发出am命令 --user 0 管理员权限
system("am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com/");
break;
}
}
}
else if(pid > 0)//父进程满足条件
{
return;
}
return;
}
JNI开发中调用C++与C的区别
- 1.源文件类型不同,.cpp,.c
-
2.调用JNIEnv对应函数方法,c++ :
//c++通过JNIEnv调用函数 env是一个一级结构体指针,比c调用少第一个参数env env->NewStringUTF("Hello From C++!"); //c调用方法 (*env)->NewStringUTF(env, buf);
-
3.c++编写jni程序需要包含javah生成头文件
#include "com_itheima_hellofromcpp_MainActivity.h" //