JNI——调用C或C++代码
JNI即JavaNativeInterface,其实就只是一个用于与C/C++代码交互的一个接口,在实际使用过程中只需要在方法名前加上native
关键字修饰。本文将从Android开发的角度阐述JNI的一些基本概念和使用。
创建第一个C/C++的App
在AndroidStudio中创建应用时选择Native C++ Project
待Project初始化完成后会自动生成一个JNI调用例子。
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
两个关键的地方,System.loadLibrary()
表示加载对应的c/c++文件,native
关键字修饰的方法stringFromJNI()
,修饰后会通过loadLibrary的C文件中寻找同名的函数。生成的cpp文件如下:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_grcen_blogdemonative_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
分析JNI调用过程
在native-lib.cpp文件中有一个与前面JNI的方法stringFromJNI()
相同的函数,首先JNIEXPORT
和JNICALL
都表示该函数和Java方法stringFromJNI()
对应,其中jstring是返回值类型,函数名的格式一般为Java_类的完整路径_native方法名
。参数env是ndk内置的一个指针/结构体,jobject表示当前对象。
这里需要明确一个概念,在c语言中的char、int等数据类型和Java中不是相同的,这里用jchar、jint等做区分。所以说当前函数虽然返回值类型是string,但我不能直接return字符串,只能使用env结构体(在C文件中为指针)下一系列的函数替我们完成从C到Java这层的转换。比如上面返回Hello from C++
这一字符串,则用NewStringUTF()
函数来完成,返回类型是jstring。下面来看个对对象参数操作的例子。
注意在cpp和c文件中JNIEnv有着不同的实现,在cpp文件中为结构体,在c文件中为指针。
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Event event = new Event("MyEvent!");
Log.i("jni", "onCreate: " + event.toString());
stringFromJNI(event);
TextView tv = findViewById(R.id.sample_text);
tv.setText(event.getMessage());
Log.i("jni", "onCreate: " + event.toString());
}
public native void stringFromJNI(Event event);
}
extern "C" JNIEXPORT void JNICALL
Java_com_grcen_blogdemonative_MainActivity_stringFromJNI(JNIEnv *env,jobject instance, jobject m_event) {
// 找到参数的类
jclass event_clazz = env->GetObjectClass(m_event);
// 利用找到的类找到属性
jfieldID message_fid = env->GetFieldID(event_clazz, "message", "java/lang/String"); //有的地方这里要改成"Ljava/lang/String"
// 利用找到的属性找到值
jstring message_value = (jstring)(env->GetObjectField(m_event, message_fid));
// 拼接字符串
const char *new_value = env->GetStringUTFChars(message_value, JNI_FALSE);
char newMessage[125] = "Oh!";
strcat(newMessage, new_value);
// 修改event对象中Message的值
jstring newMessageStr = env->NewStringUTF(newMessage);
// 修改对象的属性
env->SetObjectField(m_event, message_fid, newMessageStr);
}
在Activity中,Event对象只有一个String类型的Message对象,作为参数传入native方法。在native函数中先找到类再找到对应属性,再找其值,然后拼接字符串再修改对象中属性的值。最后在Activity中显示修改后的属性。
注意:这里与IO流中读写对象不同,这里修改完后的对象还是同一个。在将对象进行本地写操作时,再读回来,只是两个内容相同的对象但其对象已经不是同一个。在AIDL中跨进程通信时就会经常利用IO传输对象,但不能简单判断两者是否相等。
总结
总而言之我们现在可以用C/C++代码来写我们的程序了,而中间的交互由JNI完成,具体还是有JNIEnv完成。关于.so
文件和其他操作的讲解待续。