此博客是在研究完《Android内核剖析》中2.2章节JNI调用机制后,才完成的。在此非常感谢该书的作者。此书的内容较多,讲述的知识点也比较深入,值得各位Android coder们学习。
在Android应用开发时,有时候为了提升程序的效率,需要使用到JNI编程,调用native C代码,协作完成应用的某些功能。
理论讲解
JNI接口主要包含两种情况,第一种为从Java中调用C,第二种为从C中调用Java。
一. Java访问C
Java类中可以定义某些native函数。当Java编译器遇到native函数时,不会关心该函数的具体实现。在程序运行时,调用native方法前,必须将C所生成的lib库装载进来。
当调用native时,编译器会向native引擎传递调用者的包名,函数名和参数类型。在产生的C函数中,会包含至少两个参数:JNIEnv指针,指向JVM的对象,可以访问JVM内部的各种对象。第二个参数为jobject,指调用者对象。
二. C访问Java
如果C中需要使用Java的某个变量而进行相应的处理,或者C中也想调用Java中的某个函数完成某些功能,那么C就得访问Java。
Java中是没有指针的,C访问Java时只能使用特定的接口,需要将要访问的类名,函数名称和参数传递给Java引擎。其步骤如下:
1.获取Java对象的类
cls = env->GetObjectClass(jobject);
env为native函数中的第一个参数,jobject为第二个参数。
2. 获取Java函数的id值
jmethodID mid = env->GetMethodId(cls,"method_name","(Ljava/lang/String;)V");
该方法第二个参数为Java中的函数名称,第三个参数:Ljava/lang/String表示String类型的参数。
“[Ljava/lang/String;”表示String数组。返回值V代表void.
“[Ljava/lang/String;”表示String数组。返回值V代表void.
需要注意的是GetMethodID方法的格式。
jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
JNIEnv这个参数C++中不需要。clazz就是前面得到的jclass,name则是方法名称,sig是方法签名。
3. 找到函数后,开始调用函数
env->CallXXXMethod(jobject,mid,ret);
其中XXX代表函数返回值类型,具体包括Void,Object,Boolean,Byte,Char等…
通过以上三步,实现了C中调用Java函数的目标。
***************************************************************************************************************************
另外一个问题:C访问Java类中的变量时如何实现的?其实跟以上的步骤相似,也为三个步骤:
1.获取Java对象
2.获取变量的id值 .jmethodID id = env->GetFiledId(cls,"filed_name”,”I”);第三个参数为变量的类型
3. 获取变量的值
Value = env->GetXXXFiled(env,jobjcet,fid);
这个函数以返回值的方式获取变量值。
为了更好的将上述知识点表述清楚,本博客实现一个简单的demo,力争让大家理解清楚。应用实例
自定义一个类,该对象用于描述sd卡中mp3音频文件的属性。可以参看我前一篇博客内容(JNI实例1---扫描SD卡中mp3文件)native函数用于遍历MP3文件,之后调用Java的Track_Info类方法,native函数分别调用setDisplayName(),setSize(),setFilePath()等函数。
public class Track_Info implements Serializable{
private String displayName;
private long size;
private String ext;//后缀名称,eg.mp3
private String filePath;
private String parentPath;
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public long getSize() {
return size;
}
public void setSize(long size) {
// Log.e("Track_Info.java", "setSize() called in JNI, size = " + size);
this.size = size;
}
public String getExt() {
return ext;
}
public void setExt(String ext) {
this.ext = ext;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getParentPath() {
return parentPath;
}
public void setParentPath(String parentPath) {
this.parentPath = parentPath;
}
}
在Java层的native方法为:
public native void scanDir(String dirPath);
public native String[] getPathArray(String dirPath);
public native Track_Info[] getTracksArray(String dirPath);
调用该native函数时,传入sd卡目录名称(String类型),函数返回一个Track_Info对象数组。 (getTracksArray函数)
在实现这个demo时,遇到如下几个问题:
1.没有很好掌握GetMethodID函数的使用
2.JNI函数签名返回值对应表,函数签名没有很好的掌握,导致花费了不少时间排查bug。
3.使用自定义类,首先要能访问类的构造函数,当时没有调用(init),导致花费了很多时间。
jclass classTrackInfo = (*env)->FindClass(env,"com/coder80/scaner/Track_Info");
// 获取Track_Info类的构造函数ID
jmethodID midInit = (*env)->GetMethodID(env,classTrackInfo,"<init>", "()V");
//构造Track_Info对象
jobject objTrackInfo = (*env)->NewObject(env,classTrackInfo,midInit);
(类容较多,分为两篇博客进行介绍----JNI实例3---扫描SD卡中mp3文件,native函数中使用自定义的类)