Android的JNI调用(二)

    Android Studio 2.3在native下已经有了代码提示功能,按照提示下载相应组件就可以debug native代码。

一、Java调用JNI与JNI调用Java

1.1 C调用Java

    Java可以调用native层的C代码,同理C代码也可以调用Java代码,方法如下。

package com.example.jiayayao.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    JavaClass mClass = new JavaClass();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    // Example of a call to a native method
    TextView tv = (TextView) findViewById(R.id.sample_text);
    tv.setText(stringFromJNI());
    }

    private String instanceMethod() {
        return "instance method";
    }
    private static String staticMethod() {
        return "static method";
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
}
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jiayayao_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject obj) {
    // 通过对象引用获得类
    jclass clazz;
    clazz = env->GetObjectClass(obj);

    // 获得方法ID
    jmethodID instanceMethodId;
    instanceMethodId = env->GetMethodID(clazz, "instanceMethod", "()Ljava/lang/String;");
    // 执行方法
    jstring instanceMethodResult;
    instanceMethodResult = (jstring)(env->CallObjectMethod(obj, instanceMethodId));
    // 获得静态方法ID
    jmethodID staticMethodId;
    staticMethodId = env->GetStaticMethodID(clazz, "staticMethod", "()Ljava/lang/String;");
    // 执行静态方法
    jstring staticMethodResult;
    staticMethodResult = (jstring)(env->CallStaticObjectMethod(clazz, staticMethodId));

    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

    但是Java和native代码之间的转换是代价较大的操作。应尽量最小化这种转换。

1.2 域和方法描述符

    获取域ID和方法ID均分别需要域描述符和方法描述符。JDK提供命令行方式下的Java类文件反汇编程序成为javap,该工具可以从编译的类文件中解压缩域和方法描述符。javap格式及示例如下:

C:Usersjiayayao>javap -classpath D:WorkMyApplicationappuildintermediatesclassesdebugcomexamplejiayayaomyapplication -p -s MainActivity
Compiled from "MainActivity.java"
public class com.example.jiayayao.myapplication.MainActivity extends android.support.v7.app.AppCompatActivity {
  com.example.jiayayao.myapplication.JavaClass mClass;
    descriptor: Lcom/example/jiayayao/myapplication/JavaClass;
  public com.example.jiayayao.myapplication.MainActivity();
    descriptor: ()V

  protected void onCreate(android.os.Bundle);
    descriptor: (Landroid/os/Bundle;)V

  private java.lang.String instanceMethod();
    descriptor: ()Ljava/lang/String;

  private static java.lang.String staticMethod();
    descriptor: ()Ljava/lang/String;

  public native java.lang.String stringFromJNI();
    descriptor: ()Ljava/lang/String;

  static {};
    descriptor: ()V
}

    native代码注意异常处理,native代码crash会发送SIG 33信号,该信号是bionic 库栈回溯使用的。

// POSIX timers use __SIGRTMIN + 0.
// libbacktrace uses __SIGRTMIN + 1.
// libcore uses __SIGRTMIN + 2.

#define __SIGRTMIN 32

 二、局部和全局引用

2.1 局部引用

    大多数JNI函数返回局部引用。局部引用不能在后续的调用中被缓存及重用,主要因为他们的使用期限仅限于原生方法,一旦原生方法返回,局部引用即被释放。例如,FindClass函数返回一个局部引用,当原生方法返回时,它被自动释放,也可以用DeleteLocalRef函数显示释放原生代码。

    jclass clazz2;
    clazz2 = env->FindClass("java/lang/String");
    ......
    env->DeleteLocalRef(clazz2);

    根据JNI的规范,虚拟机应该允许native代码创建最少16个局部引用。在单个方法调用时进行多个内存密集型操作的最佳实践是删除未用的局部引用。如果不可能,native可以在使用之前用EnsureLocalCapacity方法请求更多的局部引用槽。

    这里需要注意的是,原来native代码中创建线程并attach出的JNIEnv不会自动释放局部引用,知道DetachCurrentThread被调用,如果这一期间的局部调用不会太多,则相安无事,但是代码中涉及到循环不停的进行JNI调用的话,一会就超额 了,所以从代码的严谨性的角度来说,局部引用建议手动释放。

2.2 全局引用

    全局引用在原生方法的后续调用过程依然有效,除非它们被原生代码显示释放。

    // globalClazz应该要保存,以便其他native函数使用
    jclass globalClazz;
    globalClazz = (jclass) env->NewGlobalRef(clazz2);
    ......
    env->DeleteGlobalRef(globalClazz);

2.3 弱全局引用

    全局引用的另一种类型是弱全局引用。与全局引用一样,弱全局引用在原生方法的后续调用过程中依然有效。与全局引用不同,弱全局引用并不组织潜在的对象被垃圾收回。因此在使用之前,要使用IsSameObject函数检验其是否仍然指向活动的类实例,不再赘述。

三、多线程

    只有原生方法执行期间及正在执行原生方法的线程环境下局部引用是有效的,局部引用不能在多线程间共享,只有全局引用可以被多个线程共享。被传递给每个原生方法的JNIEnv接口指针在于方法调用相关的线程中也是有效的,它不能被其他线程缓存或使用。

3.1 同步

    JNI的监视器允许原生代码利用Java对象同步,虚拟机保证存取监视器的线程能够安全之星,而其他线程等待监视器对象变成可用状态。

    Java代码可以使用synchronize关键字对代码块进行同步,native利用JNI的监视器方法进行同步的代码块如下:

    if(JNI_OK == env->MonitorEnter(obj)){
        /*error happens*/
    }
    /* synchronize program block*/
    if(JNI_OK == env->MonitorExit(obj)) {
        /*error happens*/
    }

    为了实现native线程与Java应用程序的通信,JNI提供了AttachCurrentThread接口,以便将原生线程附着到虚拟机上。

3.2 关于JNIEnv

  JNIEnv是一个线程相关的,也就是说线程A有个JNIEnv,线程B有个JNIEnv。由于线程相关不能在B线程中去访问线程A的JNIEnv结构体。但是全进程只有一个JavaVM对象,所以可以保存并且在任何地方使用都没有问题。调用JavaVM的AttachCurrentThread函数,就可以得到这个线程的JNIEnv结构体。这样就可以在后台线程中回调Java函数。在后台线程退出前,需要调用JavaVM的DetachCurrentThread函数来释放对应的资源。

原文地址:https://www.cnblogs.com/jiayayao/p/7187971.html