在某些情况下,java编程已经不能满足我们的需要,比如一个复杂的算法处理,这时候就需要用到jni技术;
- jni : java native interface
- jni
其实就是java和c/cpp之间进行通信的一个接口规范,java可以调用c/cpp里面的函数,同样,c/cpp也可以调用java类的方法;
jni开发工具ndk的安装:
在最新的ndk版本中,安装ndk很简单,只需要装ndk的路径配置到系统环境变量中即可;
在编译的时候,进入工程根目录;执行命令
ndk-build
即可完成编译;
一、cpp与java
>>1.java文件:TextOutput.java
package com.jnitest;import android.util.Log;
public class TextOutput {
private native String init();
static {
System.loadLibrary("text");
}public String getString() {
return init();
}public void sayHello() {
Log.e("tag", "void sayHello");
}public String sayHello_() {
Log.e("tag", "string sayHello_");
return "return string sayHello_";
}public static void sayHello__() {
Log.e("tag", "static sayHello__");
}
}
在该类中定义了一个本地方法init[native init],方法实现由接下来的cpp函数来实现;以及定义了其它几个方法,由cpp文件来调用;
>>2.cpp文件:test.cpp
#include <jni.h>
#include <android/log.h>
#include <string.h>#ifndef _Included_com_jnitest_TextOutput
#define _Included_com_jnitest_TextOutput
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_jnitest_TextOutput_init(JNIEnv *, jobject);
#ifdef __cplusplus
}
JNIEXPORT jstring JNICALL Java_com_jnitest_TextOutput_init(JNIEnv * env,
jobject obj) {
jmethodID mid; // 方法标识id
jclass cls = env->GetObjectClass(obj); // 类的对象实例
mid = env->GetMethodID(cls, "sayHello", "()V");
env->CallVoidMethod(obj, mid);jmethodID mid1;
mid1 = env->GetMethodID(cls, "sayHello_", "()Ljava/lang/String;");// @1
jstring s1 = (jstring) env->CallObjectMethod(obj, mid1); // @2jmethodID mid2;
mid2 = env->GetStaticMethodID(cls, "sayHello__", "()V");
env->CallStaticVoidMethod(cls, mid2);return s1;
}
#endif
#endif
先来看在cpp中定义的函数名:Java_com_jnitest_TextOutput_init
其实不难看出,java文件与cpp文件中函数名的配对定义方式为Java
+ 包名 + java类名 + 方法/函数名,中间用_分隔;其中两个参数分别是:
- env:当前该线程的内容,包含线程里面全部内容;
- obj:当前类的实例,指.java文件的内容(在该例子中即是TextOutput类);
@1:获取MethodId,该方法需要三个参数,分别jclass类的对象实例,函数名,函数签名;其中函数签名由两部分构成(参数+函数返回值);
查看签名可以通过先进入到java文件生成的class文件目录,执行命令:javap -s -p ClassName
生成如上图所示的函数签名;
@2:执行该方法,在上面的例子中,将执行sayHello_方法
运行程序:
TextOutput to = new TextOutput();
Log.e("tag", "" + to.getString());
二.c与java
在Activity中有一个int字段,通过callback赋值
package com.example.hellojni;import android.app.Activity;
import android.util.Log;
import android.widget.TextView;
import android.os.Bundle;public class HelloJni extends Activity {
private static int si;private static void callback() {
si = 123;
}/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText(stringFromJNI());
setContentView(tv);
Log.e("tag", "si=" + si);
}public native String stringFromJNI();
static {
System.loadLibrary("hello-jni");
}
}
hello-jni.c
#include <string.h>
#include <jni.h>/*
* */
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env,
jobject thiz) //env:当前该线程的内容,包含线程全部的东西;thiz:当前类的实例,指.java文件的内容
{
jint si;
jfieldID fid; // 一个字段,实际上对应java类里面的一个字段或属性;
jclass cls = (*env)->GetObjectClass(env, thiz); // 类的对象实例
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "callback", "()V"); // 一个方法的id
//(I)V (I)I
if (mid == NULL) {
return (*env)->NewStringUTF(env, "mid=null");
}
(*env)->CallStaticVoidMethod(env, cls, mid); //调用callback方法
fid = (*env)->GetStaticFieldID(env, cls, "si", "I"); //取出si字段
if (fid == NULL) {
return (*env)->NewStringUTF(env, "fid=null");
}
si = (*env)->GetStaticIntField(env, cls, fid); //取出字段对应的值(fid字段对应的值)return (*env)->NewStringUTF(env, "init success");
}
运行上面的Activity,即可实现对si的赋值
3.加入链接库
在程序开发过程中,会频繁的用到调试,方式有很多种,下面要讲的这一种是通过log打印信息来打印程序运行时的一些状态数值;
修改Android.mk文件,添加一句代码
include $(CLEAR_VARS)
LOCAL_LDLIBS += -llog //LDLIBS:连接libs,后面跟的参数为需要链接的libs,-llog表示Android中的Log库;
include $(BUILD_SHARED_LIBRARY)
加入了log库之后
即可以在c文件中调用log打印输出信息:
__android_log_print(ANDROID_LOG_ERROR, "hello", "livingstone");
__android_log_print(ANDROID_LOG_DEBUG, "hello", "livingstone %d" ,23);
Android jni本地编程入门