Android jni本地编程入门
在某些情况下,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); // @2 jmethodID 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);