[Android初级]NDK入门体验の方法调用

    本文属于自我复习,内容重复请勿见怪

如何在android应用的自定义底层C中调用java的代码?这是本次自我复习的计划。

首先,我得创建一个demo,结构如下:

技术分享

1.创建一个接口类NativeDataProvider,他的作用是调用c方法,然后回调java方法

public class NativeDataProvider {
	static final String TAG = "NativeDataProvider";
	private Context context;
	public NativeDataProvider(Context context){
		this.context = context;
	}
	
	//调用C中的代码
	public native void callCCode();
	
	public native int callCAdd(int i,int j);
	
	public native void callCPrintString();
	
	//在C中调用java中的org.jan.ndk.example.NativeDataProvider无参方法helloFrmJava
	public void helloFromJava(){
		Log.i(TAG, "hello from java!");
		Toast.makeText(context, "hello from java", Toast.LENGTH_SHORT).show();
	}
	//在C中调用参数为string的方法
	public void printString(String str){
		Log.i(TAG, "[from java]:"+str);
		Toast.makeText(context, "printString->str:"+str, Toast.LENGTH_SHORT).show();
	}
	//在c中调用int参数的方法
	public int add(int i,int j){
		int result = i+j;
		Log.i(TAG, "result is"+result);
		Toast.makeText(context, "add->result="+result, Toast.LENGTH_SHORT).show();
		return result;
	}
}

2.创建Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni_demo
LOCAL_SRC_FILES := jni_demo.c
#增加log库
LOCAL_LDLIBS += -llog 

include $(BUILD_SHARED_LIBRARY)
3.创建jni,也就是c代码,之前先创建NativeDataProvider的头文件,到项目目录/bin/classes下,调用javah org.jan.ndk.example.NativeDataProvider

就会生成org_jan_ndk_example_NativeDataProvider.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_jan_ndk_example_NativeDataProvider */

#ifndef _Included_org_jan_ndk_example_NativeDataProvider
#define _Included_org_jan_ndk_example_NativeDataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_jan_ndk_example_NativeDataProvider
 * Method:    callCCode
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCCode
  (JNIEnv *, jobject);

/*
 * Class:     org_jan_ndk_example_NativeDataProvider
 * Method:    callCAdd
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCAdd
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     org_jan_ndk_example_NativeDataProvider
 * Method:    callCPrintString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCPrintString
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

其中看到有Signature:()V 这样类似的 就是 方法签名。

例如:

har* classname = "org/jan/ndk/example/NativeDataProvider";
jclass dpclazz = (*env)->FindClass(env, classname);

这是通过jnienv指针获取class对象。


jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");

GetMethodID指 从java中获取定义好的method方法,第二个参数是class对象,第三个参数是方法名,第四个是方法签名(可以从头文件中看Signature)

看方法签名的方法: 进入项目\bin\classes 下,命令:javap -s org.jan.ndk.example.NativeDataProvider

"()V"的大概意思就是 括号内没有参数,就是没有入参,V就是void无返回值。

技术分享


下面是C主要代码(具体请看注释):

#include "org_jan_ndk_example_NativeDataProvider.h"
#include <string.h>
#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__)

JNIEXPORT void JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCCode
  (JNIEnv * env, jobject obj)
{
	//调用NativeDataProvider对象中的helloFromJava()方法
	//获取到某个对象, 获取对象中的方法, 调用获取到的方法
	LOGI("in code");
	//NativeDataProvider完整类名 org.jan.ndk.example.NativeDataProvider
	char* classname = "org/jan/ndk/example/NativeDataProvider";


	jclass dpclazz = (*env)->FindClass(env, classname);
	if(dpclazz == 0)
		LOGI("org.jan.ndk.example.NativeDataProvider.class not find !!!");
	else
		LOGI("org.jan.ndk.example.NativeDataProvider.class find !!!");

	//使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
	//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
	jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
	if(methodID == 0)
			LOGI("method helloFromJava not find !!!");
		else
			LOGI("method helloFromJava find !!!");

	/*
	 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
	 */
	LOGI("before call method");
	(*env)->CallVoidMethod(env, obj, methodID);
	LOGI("after call method");

}

JNIEXPORT void JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCPrintString
  (JNIEnv *env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code");
		//NativeDataProvider完整类名 org.jan.ndk.example.NativeDataProvider
		char* classname = "org/jan/ndk/example/NativeDataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		LOGI("before call method");
		(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
		LOGI("after call method");
}

/*
 * 实际开发的情况
 * C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了
 */
JNIEXPORT jint JNICALL Java_org_jan_ndk_example_NativeDataProvider_callCAdd
(JNIEnv *env, jobject obj, jint a, jint b)
{

	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code test_add=>");
		//NativeDataProvider完整类名 org.jan.ndk.example.NativeDataProvider
		char* classname = "org/jan/ndk/example/NativeDataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "add", "(II)I");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		return (*env)->CallIntMethod(env, obj, methodID, a,b);
}

4.编译jni,在demo目录下手动执行ndk-build

5.android的代码:

public class MainActivity extends ActionBarActivity {

	private static final String TAG = "MainActivity";
	private static NativeDataProvider ndp;
	
	static{
		System.loadLibrary("jni_demo");
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ndp = new NativeDataProvider(this);
		if (savedInstanceState == null) {
			getSupportFragmentManager().beginTransaction()
					.add(R.id.container, new PlaceholderFragment()).commit();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	/**
	 * A placeholder fragment containing a simple view.
	 */
	public static class PlaceholderFragment extends Fragment implements OnClickListener {
		private Button callVoidMethod, callStringMethod, callIntMethod;

		public PlaceholderFragment() {
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			View rootView = inflater.inflate(R.layout.fragment_main, container,
					false);
			callVoidMethod = (Button) rootView
					.findViewById(R.id.call_void_method_btn);
			callStringMethod = (Button) rootView
					.findViewById(R.id.call_string_parameter_method);
			callIntMethod = (Button) rootView
					.findViewById(R.id.call_int_parameter_method);
			callVoidMethod.setOnClickListener(this);
			callStringMethod.setOnClickListener(this);
			callIntMethod.setOnClickListener(this);
			return rootView;
		}

		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.call_void_method_btn:
				ndp.callCCode();
				break;
			case R.id.call_string_parameter_method:
				ndp.callCPrintString();
				break;
			case R.id.call_int_parameter_method:
				ndp.callCAdd(2,3);
				break;
			}
		}
	}

}

实现结果如下:


技术分享

代码示例下载:请打开如意门下载




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。