8.6.jni

168 阅读5分钟
  • 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; 
}

该方法的主要工作过程:

  1. 检查该动态库是否已加载;
  2. 通过dlopen打开动态共享库;
  3. 创建SharedLibrary共享库,并添加到libraries_列表;
  4. 通过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…

欢迎关注我的前端自检清单,我和你一起成长