AndroidNDK开发(五)——C代码回调Java代码 - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 技术分享 >

AndroidNDK开发(五)——C代码回调Java代码

发表时间:2020-10-19

发布人:葵宇科技

浏览次数:37


        转载请注明出处:http://blog.csdn.net/allen315410/article/details/41862479
        在上篇博客里懂得了Java层是如何传递数据到C层代码,并且熟悉了大年夜部分的实际开辟常识,根本上控制这些就可以做一个根本的NDK开辟了,然则光是懂得Java回调C层的数据是不是还不敷啊,推敲问题要推敲可逆性,Java能回调C,那么C可否反过往返调Java呢?谜底是肯定可以的,这篇博客就介绍一个C说话若何调用Java层的代码。以下是一些问题场景,我们带着这个问题场景来分析一下实现的过程。
场景1:开辟中C说话层完成了一系列操作后,须要通知Java层代码此时须要做什么操作。
场景2:大年夜家知道法度榜样员都是比较懒惰的,Java代码中封装了大年夜量的办法,C法度榜样员不想反复写复杂的逻辑,这时想经由过程C说话回调应用Java层代码中的办法。
      解释native办法callMethod4已经运行成功了。
        好,带着膳绫擎的场景,我们下面建立一个小的Demo来测验测验解决这些营业场景的问题。
package com.example.ndkcallback;

public class DataProvider {
	/**
	 * C调用java空办法
	 */
	public void nullMethod() {
		System.out.println("hello from java");
	}
	/**
	 * C调用java中的带两个int参数的办法
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public int Add(int x, int y) {
		int result = x + y;
		System.out.println("result in java " + result);
		return result;
	}
	/**
	 * C调用java中参数为String的办法
	 * 
	 * @param s
	 */
	public void printString(String s) {
		System.out.println("java " + s);
	}

	// 本处所法
	public native void callMethod1();
	public native void callMethod2();
	public native void callMethod3();

}

编译头文件


        在DOS敕令行下,切换到工程目次地点的源码存放的src目次下,应用javah敕令编译C说话的函数签名。并且得留意的是,因为我应用的JDK 是1.7版本的,所以必须得切换到工程目次/src目次下履行javah,如不雅大年夜家应用的是JDK 1.6或者JDK 1.5,那就切换到工程目次/classes目次,履行javah敕令。
[img]http://img.blog.csdn.net/20141211101935855
留意:应用javah敕令时,须要指定-encoding utf-8 参数,防止编译报乱码缺点,下面是编译好的头文件:

        有了膳绫擎的头文件,接下来就是最不好搞的C代码了,按照套路来,起首把膳绫擎编译好的头文件剪切到jni目次下,在该目次下新建一个Hello.c的C代码文件,将刚惹人的头文件的函数签名拷贝到Hello.c中应用,然后就是起首惹人LOG日记头文件,定义LOG日记输入,再然后就是编译C代码,如下:
#include<stdio.h>
#include<jni.h>
#include"com_example_ndkcallback_DataProvider.h"
#include<android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
(JNIEnv * env, jobject obj){
	//在C说话中调用Java的空办法
	//1.找到java代码native办法地点的字节码文件
	//jclass (*FindClass)(JNIEnv*, const char*);
	jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
	if(clazz == 0){
		LOGD("find class error");
		return;
	}
	LOGD("find class");
	//2.找到class琅绫擎对应的办法
	// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
	jmethodID method1 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
	if(method1 == 0){
		LOGD("find method1 error");
		return;
	}
	LOGD("find method1");
	//3.调用办法
	//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	(*env)->CallVoidMethod(env, obj, method1);
	LOGD("method1 called");
}

JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
	(JNIEnv * env, jobject obj) {
	//1.找到java代码native办法地点的字节码文件
	//jclass (*FindClass)(JNIEnv*, const char*);
	jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
	if(clazz == 0){
		LOGD("find class error");
		return;
	}
	LOGD("find class");
	//2.找到class琅绫擎对应的办法
	// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
	jmethodID method2 = (*env)->GetMethodID(env,clazz,"Add","(II)I");
	if(method2 == 0){
		LOGD("find method2 error");
		return;
	}
	LOGD("find method2");
	//3.调用办法
	//jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
	int result = (*env)->CallIntMethod(env, obj, method2, 3,5);
	LOGD("result in C = %d", result);
}

JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3
(JNIEnv * env, jobject obj) {
	//1.找到java代码native办法地点的字节码文件
	//jclass (*FindClass)(JNIEnv*, const char*);
	jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
	if(clazz == 0){
		LOGD("find class error");
		return;
	}
	LOGD("find class");
	//2.找到class琅绫擎对应的办法
	// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
	jmethodID method3 = (*env)->GetMethodID(env,clazz,"printString","(Ljava/lang/String;)V");
	if(method3 == 0){
		LOGD("find method3 error");
		return;
	}
	LOGD("find method3");
	//3.调用办法
	//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	(*env)->CallVoidMethod(env, obj, method3,(*env)->NewStringUTF(env,"haha in C ."));
	LOGD("method3 called");
}
留意:编写C代码时大年夜致须要如下3个重要的步调:
jclass (*FindClass)(JNIEnv*, const char*);

1.找到java代码native办法地点的字节码文件,在jni.h中的JNINativeInterface中可以找到

     个中第1个参数是JNINativeInterface的指针env,第2个参数是java办法地点的类全路径名,路径之间用“/”来区分,弗成以应用“.”
2.找到class琅绫擎对应的办法,在jni.h中的JNINativeInterface中可以找到
获取风静态办法id:
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

获取静态办法id:
jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);


      个中第1个参数是JNINativeInterface的指针env,第2个参数是java字节码文件,第3个参数是java中的办法名,第四个参数是java中对应办法的签名。
3.调用办法

void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
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, ...);
jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
       个中第1个参数是JNINativeInterface的指针env,第2个参数是java对象obj,第3个参数是找到的对应java中的办法,第4个参数是办法接收的参数。这里列出的是常用的办法,jni.h里的JNINativeInterface供给了大年夜量的办法情势用往返调java中的办法,想懂得的请参考jni.h这个文件。

应用javap敕令查看办法签名


       JDK为我们供给了如许的一个对象,该对象可以大年夜java字节码文件中查看办法的本地签名,这个对象就是javap,应用前,先在CMD的dos敕令行中,把路径切换到工程中的java字节码文件地点的目次下。
       敕令格式:javap -s 包名.办法地点的Java类名
[img]http://img.blog.csdn.net/20141211151225651
如图所示的那样,黄色标注的是办法名,是(*GetMethodID)(JNIEnv*, jclass, const char*, const char*)中的第3个参数,红色标注的是办法签名,是其第4个参数。

Android.mk设备和Application.mk设备


    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE    := Hello

    LOCAL_SRC_FILES := Hello.c
    
    LOCAL_LDLIBS += -llog

    include $(BUILD_SHARED_LIBRARY)
APP_PLATFORM := android-8

编译C代码


起首在cygwin中切换到当前工程目次下,履行“ndk-build clean”和“ndk-build”敕令
[img]http://img.blog.csdn.net/20141211152804367

在Java中调用Nattive办法

public class MainActivity extends Activity implements OnClickListener {

	static {
		// 加载动态库.so
		System.loadLibrary("Hello");
	}
	private Button btn1, btn2, btn3;
	private DataProvider provider;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btn1 = (Button) findViewById(R.id.btn1);
		btn2 = (Button) findViewById(R.id.btn2);
		btn3 = (Button) findViewById(R.id.btn3);
		btn1.setOnClickListener(this);
		btn2.setOnClickListener(this);
		btn3.setOnClickListener(this);
		provider = new DataProvider();
	}
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
			case R.id.btn1 : // c回调java中的空办法
				provider.callMethod1();
				break;
			case R.id.btn2 :// c回调java带2个int参数的办法
				provider.callMethod2();
				break;
			case R.id.btn3 :// c回调java带string参数的办法
				provider.callMethod3();
				break;
			default :
				break;
		}
	}

}

测试


[img]http://img.blog.csdn.net/20141211153429001
留意:以下测试的LOG中,绿色代表Java生成的LOG,蓝色代表C生成的LOG。
测试1:c回调java中的空办法
[img]http://img.blog.csdn.net/20141211153444265
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndkcallback_DataProvider */

#ifndef _Included_com_example_ndkcallback_DataProvider
#define _Included_com_example_ndkcallback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndkcallback_DataProvider
 * Method:    callMethod1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
(JNIEnv *, jobject);

/*
 * Class:     com_example_ndkcallback_DataProvider
 * Method:    callMethod2
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
(JNIEnv *, jobject);

/*
 * Class:     com_example_ndkcallback_DataProvider
 * Method:    callMethod3
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

编写C代码


测试2:c回调java带2个int参数的办法
[img]http://img.blog.csdn.net/20141211153448189
测试3:c回调java带string参数的办法
[img]http://img.blog.csdn.net/20141211153500843
JNIEXPORT void JNICALL Java_com_example_ndkcallback_MainActivity_callMethod4
  (JNIEnv * env, jobject obj){
	//1.找到java代码native办法地点的字节码文件
		//jclass (*FindClass)(JNIEnv*, const char*);
		jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
		if(clazz == 0){
			LOGD("find class error");
			return;
		}
		LOGD("find class");
		//2.找到class琅绫擎对应的办法
		// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
		jmethodID method4 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
		if(method4 == 0){
			LOGD("find method4 error");
			return;
		}
		LOGD("find method4");
		//3.经由过程jclass获取jobject
		//jobject     (*AllocObject)(JNIEnv*, jclass);
		jobject jobj = (*env)->AllocObject(env, clazz);
		if(jobj == 0){
			LOGD("find jobj error");
			return;
		}
		LOGD("find jobj");
		//4.调用办法
		//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		(*env)->CallVoidMethod(env, jobj, method4);
		LOGD("method4 called");
}
      写完代码之后,从新编译C代码文件,Refresh和clean一下工程,运行后:

别的:native代码与调用的java代码不在同一个类里


       上述建立的Android工程中,native代码和调用的java代码是放在同一个DataProvider类中的,如许在C代码中调用Java代码是异常便利的。然则,平日开辟中我们不必定就这么干,一个项目中java文件很多,如果在其它的java文件中定义了native办法了,然后再去调另一个java类里的Java办法,这种情况下会出现什愦问题呢?带着这个疑问,我们就在MainActivity.java文件中定义一个native办法,这个native办法又要调用DataProvider类的nullMethod办法。
在MainActivity.java中,我们定义如许的办法:
private native void callMethod4();
切换到这个src目次下javah获取函数签名,将获得的签名头文件拷贝到jni目次下,在C文件中引用这个头文件,编写响应的C代码:
JNIEXPORT void JNICALL Java_com_example_ndkcallback_MainActivity_callMethod4
  (JNIEnv * env, jobject obj){
	//1.找到java代码native办法地点的字节码文件
		//jclass (*FindClass)(JNIEnv*, const char*);
		jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
		if(clazz == 0){
			LOGD("find class error");
			return;
		}
		LOGD("find class");
		//2.找到class琅绫擎对应的办法
		// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
		jmethodID method4 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
		if(method4 == 0){
			LOGD("find method4 error");
			return;
		}
		LOGD("find method4");
		//3.调用办法
		//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		(*env)->CallVoidMethod(env, obj, method4);
		LOGD("method4 called");
}
编译运行之后,报错了
[img]http://img.blog.csdn.net/20141211171250055
       实际运行的时刻,法度榜样直接崩溃了,查看日记发明,字节码class找到了,办法method找到了,然则就是没有履行method办法,显然是履行method办法这行代码出了Bug,以下是调用method办法履行的代码:
(*env)->CallVoidMethod(env, obj, method4);
       那么这行代码是为什么报错了呢?细心不雅察一下,CallVoidMethod办法的第2个参数obj,这个obj是jobject类型的,默认是java native办法地点的类的对象,就是MainActivity类的对象,然则这个native办法实际上调用的java办法存在于DataProvider类的nullMethod,调用nullMethod显然须要应用DataProvider类的对象。反正就一句话:obj对象不精确,须要java办法对应的对象,即DataProvider。

创建工程,在工程琅绫擎定义Java办法和Native办法


       知道问题了,就可以着手解决问题了。在jni.h的头文件中,JNINativeInterface供给了如许的一个办法,赞助我们经由过程字节码jclass找到对应的对象:
jobject     (*AllocObject)(JNIEnv*, jclass);
       这个办法第1个参数是JNINativeInterface,第2个参数是jclass,返回值jobject。我们就拿这个办法获取jobject,传给CallVoidMethod:

[img]http://img.blog.csdn.net/20141211173234721
源码请在这里下载

相关案例查看更多