- JNI(Java Native Interface,Java本地接口),用于打通Java层与Native(C/C++)层;
- Java语言是跨平台的语言,而这跨平台的背后都是依靠Java虚拟机,虚拟机采用C/C++编写,适配各个系统,通过JNI为上层Java提供各种服务,保证跨平台性。
1. System.loadLibrary(“media_jni”)原理
1.1 loadLibrary
[System.java]
public static void loadLibrary(String libName) {
//调用Runtime方法
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
[Runtime.java]
void loadLibrary(
String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
//加载库【见小节1.2】
String error = doLoad(filename, loader);
if (error != null) { throw new UnsatisfiedLinkError(error); }
return;
}
//loader为空,则会进入该分支
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
//加载库【见小节1.2】
String error = doLoad(candidate, loader);
if (error == null) { return; //加载成功 }
}
}
...
}
1.2 doLoad
private String doLoad(String name, ClassLoader loader) {
...
//该方法内部增加同步锁,保证并发时一致性。
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
1.3 nativeLoad
nativeLoad()这是一个native方法,再进入ART虚拟机的java_lang_Runtime.cc
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPathJstr) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == nullptr) { return nullptr; }
SetLdLibraryPath(env, javaLdLibraryPathJstr);
std::string error_msg;
{
JavaVMExt* vm = Runtime::Current()->GetJavaVM();
//[见小节1.4]
bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
if (success) { return nullptr; }
}
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
1.4 LoadNativeLibrary
[-> java_vm_ext.cc]
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) {
error_msg->clear();
SharedLibrary* library;
Thread* self = Thread::Current();
{
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path); //检查该动态库是否已加载
}
if (library != nullptr) {
if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) {
//不能加载同一个采用多个不同的ClassLoader
return false;
}
...
return true;
}
const char* path_str = path.empty() ? nullptr : path.c_str();
//通过dlopen打开动态共享库.该库不会立刻被卸载直到引用技术为空.
void* handle = dlopen(path_str, RTLD_NOW);
bool needs_native_bridge = false;
if (handle == nullptr) {
if (android::NativeBridgeIsSupported(path_str)) {
handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
needs_native_bridge = true;
}
}
if (handle == nullptr) {
*error_msg = dlerror();
//打开失败
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false;
}
bool created_library = false;
{
std::unique_ptr<SharedLibrary> new_library( new SharedLibrary(env, self, path, handle, class_loader));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
if (library == nullptr) {
library = new_library.release(); //创建共享库,并添加到列表 libraries_->Put(path, library);
created_library = true;
}
}
...
bool was_successful = false;
void* sym;
//查询JNI_OnLoad符号所对应的方法
if (needs_native_bridge) {
library->SetNeedsNativeBridge();
sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
} else {
sym = dlsym(handle, "JNI_OnLoad");
}
if (sym == nullptr) {
was_successful = true;
} else {
//需要先覆盖当前ClassLoader.
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
// 真正调用JNI_OnLoad()方法的过程
int version = (*jni_on_load)(this, nullptr);
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
fault_manager.EnsureArtActionInFrontOfSignalChain();
}
//执行完成后, 需要恢复到原来的ClassLoader
self->SetClassLoaderOverride(old_class_loader.get());
...
}
library->SetResult(was_successful);
return was_successful;
}
该方法的主要工作过程:
- 检查该动态库是否已加载;
- 通过dlopen打开动态共享库;
- 创建SharedLibrary共享库,并添加到libraries_列表;
- 通过dlsym获取JNI_OnLoad符号所对应的方法, 并调用该方法.
2.java调用native方法
class NativeDemo {
public native String encryptData (String inputdata);
static {
System.loadLibrary ("nativedemo"); /* lowercase of classname! */
}
public static void main (String[] args){
NativeDemo demo = new NativeDemo ();
System.out.println("Encrypted data is " + demo.encryptData ("This is javakk"));
}
}
3. 静态动态注册
1、按照JNI规范的命名规则进行查找,这种方式叫静态注册。
2、调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中,这种方式叫动态注册。
3.1 静态注册
所谓静态注册就是按照JNI规范书写函数名:
java_类路径_方法名(路径用下划线分隔)
当我们使用Android Studio新建一个Native工程时默认生成的JNI函数就是静态注册的,例如下面就是一个静态注册的简单例子:
extern "C" JNIEXPORT jstring JNICALL
Java_com_fly_jnitest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject obj) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
静态注册的方式是系统的默认方式,使用简单,但是灵活性比较差,如果修改了Java中的Native函数所在类的包名或类名,则需要同时修改C/C++函数名称(头文件、源文件等)。
3.2 动态注册
在库加载时会自动调用JNI_OnLoad()函数,开发者经常会JNI_OnLoad()函数做一些初始化操作,动态注册就是在这里进行的。调用API是env->RegisterNatives(clazz, gMethods, numMethods)。
env->RegisterNatives(clazz, gMethods, numMethods)是一个接受三个参数的函数,第一个参数是Java对应的类,第二个参数是JNINativeMethod数组,第三个参数是JNINativeMethod数组的长度,也就是需要注册的方法的个数。
其中JNINativeMethod表示的是方法方法的映射关系,它包括Java中的方法名,对应的方法签名和Native映射的函数方法三个信息。
相比静态注册,动态注册的灵活性更高,如果修改了java native函数所在类的包名或类名,仅调整Java native函数的签名信息即可。 // 前两个参数还是固定的
jstring stringFromJNI(JNIEnv *jniEnv,jobject jobj){
return jniEnv->NewStringUTF("hello from C++ string");
}
static const JNINativeMethod nativeMethod[] = {
// Java中的函数名
{"stringFromJNI",
// 函数签名信息
"()Ljava/lang/String;",
// native的函数指针
(void *) (stringFromJNI)},
};
// 类库加载时自动调用
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reversed)
{
JNIEnv *env = NULL;
// 初始化JNIEnv
if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
return JNI_FALSE;
}
// 找到需要动态动态注册的Jni类
jclass jniClass = env->FindClass("com/fly/jnitest/MainActivity");
if(nullptr == jniClass){
return JNI_FALSE;
}
// 动态注册
env->RegisterNatives(jniClass,nativeMethod,sizeof(JNINativeMethod)/sizeof(nativeMethod));
// 返回JNI使用的版本
return JNI_VERSION_1_6;
}
4.在c/c++中调用Java方法
public class Sample2 {
public String name;
public static String sayHello(String name) {
return "Hello, " + name + "!";
}
public String sayHello() {
return "Hello, " + name + "!";
}
}
#include <jni.h>
#include <string.h>
#include <stdio.h>
// 环境变量PATH在windows下和linux下的分割符定义
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif
int main(void)
{
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj;
options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (status != JNI_ERR)
{
// --- 1. 先获得class对象
cls = (*env)->FindClass(env, "Sample2");
if (cls != 0)
{
// --- 2.获取方法ID, 通过方法名和签名, 调用静态方法
mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid != 0)
{
const char* name = "World";
jstring arg = (*env)->NewStringUTF(env, name);
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
const char* str = (*env)->GetStringUTFChars(env, result, 0);
printf("Result of sayHello: %s\n", str);
(*env)->ReleaseStringUTFChars(env, result, 0);
}
// --- 3. 调用指定的构造函数, 构造函数的名字叫做<init>,创建该类的对象
mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
if (obj == 0)
{
printf("Create object failed!\n");
}
// --- 4.获取属性ID, 并修改属性
fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
if (fid != 0)
{
const char* name = "icejoywoo";
jstring arg = (*env)->NewStringUTF(env, name);
(*env)->SetObjectField(env, obj, fid, arg);
}
// --- 5.获取要调用的java方法的句柄,调用成员方法
mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
if (mid != 0)
{
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
const char* str = (*env)->GetStringUTFChars(env, result, 0);
printf("Result of sayHello: %s\n", str);
(*env)->ReleaseStringUTFChars(env, result, 0);
}
}
(*jvm)->DestroyJavaVM(jvm);
return 0;
}
else
{
printf("JVM Created failed!\n");
return -1;
}
}
参考:
gityuan.com/2016/05/28/…
www.cnblogs.com/goFlyer/art…
欢迎关注我的前端自检清单,我和你一起成长