Android NDK开发之C、java互调

笔记篇

熟练javah 、javap命令
配置生成dll方法

windows通过JNI调用dll动态库

  • 第一步定义native方法
  • 第二步通过javah命令生成.h头文件
  • 第三步将生成.h头文件复制到C项目当中(vs)
  • 第四步导入jni支持头文件(将jni.h、jni_md.h复制到C项目中,以上两个文件在jdk安装目录下)
  • 第五步实现native方法(实际上在c中实现)
  • 第六步配置属性,生成dll动态库
    • 6.0 解决方案—>属性—>常规—>配置类型—>dll
    • 6.1 生成—>配置管理器—>设置平台架构为x64、x32
    • 6.1 生成—>生成解决方案
  • 第七步配置dll动态库所在的目录(配置环境变量:目的为了在java中能够调用到)
  • 第八步重启eclipse(目的:为了让eclipse重新读取系统环境变量配置)
  • 第九步启动运行

如果你不想这么麻烦每一次都去配置环境变量,那么你就固定配置一个环境变量目录
之后的这些dll动态库,拷贝下去就好了

分析JNI方法

原始写法

JNIEXPORT jstring JNICALL Java_dllProject_DllMain_getTextString(JNIEnv *env, jobject jobj){
	return (*env)->NewStringUTF(env, "Hello Jni!");
}
  • 1.1 分析JNIEXPORT:宏定义(作用:dll动态库中允许该函数被外部调用)
    为什么要这些?行业规范(编译动态库规范)–固定格式
  • 1.2 分析JNICALL:用于约束函数入栈顺序和堆栈清理规则(注意:windows下的规则__stdcall)
    如果删除JNIEXPORT和JNICALL也不会报错,系统编译时候自动加上
  • 1.3 分析方法名称含义:Java_com_tz_jni_Test_getTextString
    一种命名规范(区分方法)(说白了就是唯一标识)
  • 1.4 分析JNIEnv是什么?干了什么事情?
    JNIEnv是一个结构体指针(指针也是变量,也可以存储指针地址)
    目的:通过JNIEnv访问Java中的方法,属性,创建对象,加载类等等…
  • 1.5 分析jobject(代表了什么?什么含义)
    两张情况(jobject有两张含义)
    • 第一种情况:如果你的native方法不是静态方法,那么jobject代表该方法对应的java对象
      (例如:说白了就是我们java中调用的test对象)
    • 第二种情况:如果你的native方法是静态方法,那么jobject代表该方法对应的class对象

初体验

#define _CRT_SECURE_NO_WARNINGS
//注意:导入系统的头文件<>,导入自己头文件""
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dllProject_DllMain.h"

JNIEXPORT jstring JNICALL Java_dllProject_DllMain_getTextString
(JNIEnv *env, jobject jobj){
	return (*env)->NewStringUTF(env, "Hello Jni!");
}

入门属性篇

C访问Java成员(访问实例属性)


JNIEXPORT jstring JNICALL Java_dllProject_DllMain_updateName
(JNIEnv *env, jobject jobj){
	//在C中不能够通过实例调用属性(说白了就是反射)
	//class对象(类对象)
	jclass cls = (*env)->GetObjectClass(env, jobj);
	//获取属性(java中称之为属性对象)
	//参数一:JNIEnv *env
	//参数二:class对象
	//参数三:属性名称
	//参数四:属性签名(说白了就是去指定类型)
	//属性签名就是类型的简写
	jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
	//获取属性的值(类似java反射中)
	//注意:Get<签名类型>Field
	//参数一:JNIEnv *env
	//参数二:哪一个对象--jobj
	//参数三:哪一个属性--fid
	//返回值:就是我们的jstring(在java中所有的类都是object子类)
	//在jni中也是,所有的都是jobject的子类
	jstring jstr = (*env)->GetObjectField(env, jobj, fid);

	//将jni的类型转成C/C++中的数据类型
	char* str = (*env)->GetStringUTFChars(env, jstr, NULL);
	
	//更新str
	char newStr[] = "  Hello jni";
	strcat(str, newStr);

	//又要将C/C++数据类型转成jni类型
	jstr = (*env)->NewStringUTF(env, str);

	//更新java对象中的属性的值
	(*env)->SetObjectField(env, jobj, fid, jstr);

	return jstr;
}

C/C++访问Java中的静态属性


JNIEXPORT void JNICALL Java_dllProject_DllMain_updateAge
(JNIEnv *env, jobject jobj){
	//在C中不能够通过实例调用属性(说白了就是反射)
	//class对象(类对象)
	jclass cls = (*env)->GetObjectClass(env, jobj);
	//获取属性
	jfieldID fid = (*env)->GetStaticFieldID(env, cls, "age", "I");
	//获取值
	//注意:GetStatic<类型>Field
	jint age = (*env)->GetStaticIntField(env, cls, fid);
	//jint就是long类型,可以直接操作
	age += 50;
	//更新java静态属性值
	//注意:SetStatic<类型>Field
	(*env)->SetStaticIntField(env, cls, fid, age);
}

入门方法篇

C/C++访问java实例方法

//小案例:生成随机数
JNIEXPORT jint JNICALL Java_dllProject_DllMain_getNumber
(JNIEnv *env, jobject jobj){
	//在C中不能够通过实例调用属性(说白了就是反射)
	//class对象(类对象)
	jclass cls = (*env)->GetObjectClass(env, jobj);

	//获取实例方法
	//javap -s -p 类名
	jmethodID mid = (*env)->GetMethodID(env, cls, "getRandomInt", "(I)I");
	//调用方法(执行方法)类似于java中的invoke
	//参数一:env
	//参数二:实例对象
	//参数三:调用哪一个方法
	//参数四:传入的参数列表
	jint randomInt = (*env)->CallIntMethod(env, jobj, mid, 100);
	return randomInt;
}

C/C++访问java静态方法

//javap命令,能够获取签名
JNIEXPORT void JNICALL Java_dllProject_DllMain_executeRandomUUID
(JNIEnv *env, jobject jobj){
	//在C中不能够通过实例调用属性(说白了就是反射)
	//class对象(类对象) Class<?> clz
	jclass cls = (*env)->GetObjectClass(env, jobj);
	//获取静态方法
	jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
	//执行方法
	jstring jstr = (*env)->CallStaticObjectMethod(env, cls, mid);
	//将jstring转成c的字符串
	char* uuid = (*env)->GetStringUTFChars(env, jstr, NULL);
	//可以将值写到一个文件中
	char path[100];
	sprintf(path, "F://%s.txt", uuid);
	//将uuid写入文件中
	FILE*f = fopen(path, "w");
	fputs(uuid, f);
	fclose(f);
}

java编码

需要返回包的根目录,并执行如下命令,生成头文件。 javah -d …/jni dllProject.DllMain

public class DllMain {
	public String name = "Zero";

	public static int age = 18;

	public native String getTextString();

	public native String updateName();

	public native void updateAge();

	public native int getNumber();

	public native void executeRandomUUID();

	public static void main(String[] args) {
		DllMain test = new DllMain();
		System.out.println(test.getTextString());
		// 一旦调用了该方法,在C中更新name
		test.updateName();
		System.out.println("更新后的值:" + test.name);
		test.updateAge();
		System.out.println("更新后的age值:" + age);

		System.out.println("得到一个数:" + test.getNumber());

		test.executeRandomUUID();
	}

	/**
	 * 生成一个随机数
	 * 
	 * @param max
	 * @return
	 */
	public int getRandomInt(int max) {
		Random rd = new Random();
		return rd.nextInt(max);
	}

	/**
	 * 生成一个随机字符串
	 * 
	 * @return
	 */
	public static String getUUID() {
		return UUID.randomUUID().toString();
	}

	// 加载库
	static {
		// 加载动态库
		// Windows系统下:.dll
		// Linux环境下:.so(Android)
		// 加载dll动态库,不需要后缀(原名称:dllTest.dll)
		System.loadLibrary("dllTest");
	}
}

进阶篇

访问构造函数

//1、在C语言中访问Java的构造方法
JNIEXPORT jobject JNICALL Java_dllProject_test2_JniTest_callCustructor
(JNIEnv *env, jobject jobj, jstring jpath){

	//参数一:JNIEnv *env(包含了所有NDK相关函数)
	//参数二:完整类名
	jclass cls = (*env)->FindClass(env, "dllProject/test2/Company");
	//创建一个对象(我要执行cls中的构造方法)
	//获取构造方法
	//<init>:代表就是调用构造方法(固定写法)
	//()V:无参数构造方法签名
	jmethodID init_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
	//根据构造方法创建对象
	jobject company_obj = (*env)->NewObject(env, cls, init_mid);

	//调用Java Company类中的salary()--发工资方法
	//获取salary()方法对象(对象方法)
	jmethodID salary_mid = (*env)->GetMethodID(env, cls, "salary", "()V");
	//执行该方法
	(*env)->CallVoidMethod(env, company_obj, salary_mid, NULL);

	//获取当前时间戳作为文件的名称,然后写一句话
	//首先获取getTime方法
	jmethodID gettime_mid = (*env)->GetMethodID(env, cls, "getTime", "()Ljava/lang/String;");
	//执行方法(java的string)
	jstring jtime_str = (*env)->CallObjectMethod(env, company_obj, gettime_mid, NULL);
	//将String转成C中的认识的字符数组
	const char *time_str = (*env)->GetStringUTFChars(env, jtime_str, NULL);
	//获取SD卡路径
	const char *path_str = (*env)->GetStringUTFChars(env, jpath, NULL);

	char file_path[100] = { 0 };
	//在路径中拼接文件名称
	strcpy(file_path, path_str);
	strcat(file_path, "/log.txt");
	//strintf(file_path,"/%c.txt",time_str);
	//写入文件中
	FILE *fp = fopen(file_path, "w");
	fputs("今天我不高兴,你的礼物我不收了!", fp);
	fclose(fp);
	
	return company_obj;
}

C中访问Java的父(子)类方法


//打印日志
void customerPrintfLog(JNIEnv *env, const char *path_str, const char *content){
	//获取SD卡路径
	char file_path[100] = { 0 };
	//在路径中拼接文件名称
	strcpy(file_path, path_str);
	strcat(file_path, "/log.txt");
	//strintf(file_path,"/%c.txt",time_str);
	//写入文件中
	FILE *fp = fopen(file_path, "w");
	fputs(content, fp);
	fclose(fp);
}


//C中访问Java的父类方法
//首先访问子类方法,然后在访问父类方法
JNIEXPORT void JNICALL Java_dllProject_test2_JniTest_callSuperMethod
(JNIEnv *env, jobject jobj, jstring jpath){
	const char *path_str = (*env)->GetStringUTFChars(env, jpath, NULL);
	//首先获取company属性
	//cls->NDKTest类对象
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetFieldID(env, cls, "company", "LdllProject/test2/Company;");
	//获取属性的值(company_obj = new GoogleCompany())
	jobject company_obj = (*env)->GetObjectField(env, jobj, fid);
	//Company对应的类对象(GoogleCompany.class)
	jclass company_cls = (*env)->GetObjectClass(env, company_obj);
	jclass super_cls = (*env)->GetSuperclass(env, company_cls);

	//调用company_obj上salary方法
	jmethodID salary_mid = (*env)->GetMethodID(env, super_cls, "salary", "()V");
	//执行方法
	//注意:这个地方你执行的是方法(CallVoidMethod,而不是父类方法
	//CallNonvirtualVoidMethod:执行父类对象方法
	//CallVoidMethod:执行当前对象的方法
	(*env)->CallVoidMethod(env, company_obj, salary_mid, NULL);
	(*env)->CallNonvirtualVoidMethod(env, company_obj, super_cls, salary_mid, NULL);
	customerPrintfLog(env, path_str, "5");

}

中文乱码问题

JNIEXPORT jstring JNICALL Java_dllProject_test2_JniTest_getCNChars
(JNIEnv *env, jobject jobj, jstring jpath, jstring text){
	/*//直接将文本输出到文件中
	const char *str = (*env)->GetStringUTFChars(env, text, NULL);
	const char *path = (*env)->GetStringUTFChars(env, jpath, NULL);
	customerPrintfLog(env, path, str);

	//不同的环境编译生成动态库编码不一样
	//Windows环境编译:GB2312(生成的是dll动态库)
	//Window下注意:项目编码要是UTF-8、GBK编码,NDK中返回的编码GB2312
	//Mac os(Linux环境):支持中文(直接开发,不需要转),如果你要进行编码
	//那么编码要和项目编码一致,要不然乱码
	return (*env)->NewStringUTF(env, "我不是富二代,至少要成为富二代他爹");
	*/

	char *cstr = "我说中文";
	jclass str_cls = (*env)->FindClass(env, "java/lang/String");
	jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");

	//char * -> char[] ->jbyteArray -> jbyte * 
	jbyteArray bytes = (*env)->NewByteArray(env, strlen(cstr));
	//bytes数组赋值
	(*env)->SetByteArrayRegion(env, bytes, 0, strlen(cstr), cstr);


	//jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
	jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

	//返回GB2312中文编码jstring
	return (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName);
}

Java编码

Company.java


public class Company {

	private String companyName;

	public Company() {
		System.out.println("调用了构造方法......");
	}

	public Company(String companyName) {
		this.companyName = companyName;
	}

	public void salary() {
		System.out.println("公司发工资了!我很高兴,又可以给妹子买礼物");
	}

	public String getTime() {
		return String.valueOf(System.currentTimeMillis());
	}

	public String getCompanyName() {
		return companyName;
	}

	public void setCompanyName(String companyName) {
		this.companyName = companyName;
	}
}

GoogleCompany.java

public class GoogleCompany extends Company {

	@Override
	public void salary() {
		System.out.println("Google公司发工资......");
	}

}

JniTest.java

public class JniTest {
	// C访问Java构造方法(通过C创建Company对象)
	public native Company callCustructor(String path);

	// 向SD卡写日志信息
	public native void callSuperMethod(String path);

	// 中文乱码问题分析
	public native String getCNChars(String path, String text);

	private Company company = new GoogleCompany();
	static {
		System.loadLibrary("dllTest");
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值