Android jni知识点

个人主页:https://chengang.plus/

文章将会同步到个人微信公众号:Android部落格

1、创建jni环境

https://developer.android.com/studio/projects/gradle-external-native-builds

https://developer.android.com/training/articles/perf-jni

https://www.jianshu.com/p/127adc130508

https://www.jianshu.com/p/d2e3e5632cc9

1.1 配置方式

在使用jni代码之前需要先配置jni,不论使用ndk-build还是cmake都需要在build.gradle文件中配置:

1.1.1 ndkBuild

这里以ndkBuild方式为例,cmake可以参照本章开始的第一个链接。

android {
    compileSdkVersion 28
    defaultConfig {
        externalNativeBuild {
            ndkBuild {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
                abiFilters "armeabi-v7a", "arm64-v8a"
            }
        }
    }
    
    externalNativeBuild {
        ndkBuild {
            path 'src/main/java/jni/Android.mk'
        }
    }
}
1.1.2 cmake

https://developer.android.com/studio/projects/configure-cmake

https://developer.android.com/studio/projects/add-native-code

在module根目录右键,创建file,名称是“CMakeLists.txt”,然后往里面添加配置内容:

cmake_minimum_required(VERSION 3.4.1)

add_library( # Specifies the name of the library.
        communicate

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/java/jni/DynamicRegister.c src/main/java/jni/coffeecatch.c src/main/java/jni/coffeejni.c)

find_library( # Defines the name of the path variable that stores the
        # location of the NDK library.
        log-lib

        # Specifies the name of the NDK library that
        # CMake needs to locate.
        log)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
        communicate

        # Links the log library to the target library.
        ${log-lib})

这里的communicate就是生成的so库名称,最终是libcommunicate.so。

在module目录下的build.gradle文件中添加cmake配置:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        externalNativeBuild {
            cmake {
                version "3.10.2"
                abiFilters "armeabi-v7a", "arm64-v8a"
                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
            }
        }
    }
    
    externalNativeBuild {
        cmake {
            // Provides a relative path to your CMake build script.
            path "CMakeLists.txt"
        }
    }
}

android节点下的externalNativeBuild中,配置的path路径对应新建的CMakeLists.txt文件路径。

1.2 调用方式

分为java调用jni层和jni层调用java。下面的代码包括了一般情况下的调用方式:

JNIEXPORT jstring
Java_com_example_chuckapptestdemo_TestJniActivity_communicate2Jni(JNIEnv *env, jobject obj, jstring message) {
    const char *nativeString = (*env)->GetStringUTFChars(env, message, 0);
    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s", nativeString);
    (*env)->ReleaseStringUTFChars(env, message, nativeString);

    invokeJavaMethod(env, obj);

    jstring response = (*env)->NewStringUTF(env, "jni say hi to java");
    return response;
}

void invokeJavaMethod(JNIEnv *env, jobject obj) {
    //实例化TestBeInvoked类
    jclass testclass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
    //构造函数的方法名为<init>
    jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
    //根据构造函数实例化对象
    jobject testobject = (*env)->NewObject(env, testclass, testcontruct);

    //调用成员方法,需使用jobject对象
    jmethodID test = (*env)->GetMethodID(env, testclass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testobject, test, 1);

    //调用静态方法
    jmethodID testStatic = (*env)->GetStaticMethodID(env, testclass, "testStatic",
                                                     "(Ljava/lang/String;)V");
    //创建字符串,不能在CallStaticVoidMethod中直接使用"hello world!",会报错的
    jstring str = (*env)->NewStringUTF(env, "hello world!");
    //调用静态方法使用的是jclass,而不是jobject
    (*env)->CallStaticVoidMethod(env, testclass, testStatic, str);

    jmethodID innerClassInstance = (*env)->GetMethodID(env, testclass, "getInnerClass",
                                                       "()Lcom/example/chuckapptestdemo/TestBeInvoked$TestInnerClass;");
    if (innerClassInstance == 0) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Communicate innerClassInstance == 0");
    }
    jobject innerObject = (*env)->CallObjectMethod(env, testobject, innerClassInstance);
    if (innerObject == 0) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Communicate innerObject == 0");
    }
    //实例化InnerClass子类
    jclass innerclass = (*env)->FindClass(env,
                                          "com/example/chuckapptestdemo/TestBeInvoked$TestInnerClass");
    //调用子类的成员方法
    jmethodID setInt = (*env)->GetMethodID(env, innerclass, "testInner", "(I)V");
    (*env)->CallVoidMethod(env, innerObject, setInt, 2);
}

上述调用对应的java层的代码如下:

public class TestBeInvoked {
    final String TAG = "Communicate";

    public TestBeInvoked() {
        innerClass = new TestInnerClass();
        Log.d(TAG, "TestBeInvoked constructor is invoked");
    }

    public void test(int number) {
        Log.d(TAG, "test is invoked:" + number);
    }

    public static void testStatic(String message) {
        Log.d("Communicate", "testStatic is invoked:" + message);
    }

    private TestInnerClass innerClass;

    public TestInnerClass getInnerClass() {
        Log.d("Communicate", "getInnerClass is invoked");
        return innerClass;
    }

    public class TestInnerClass {

        public TestInnerClass() {
            Log.d(TAG, "TestInnerClass constructor is invoked");
        }

        public void testInner(int number) {
            Log.d(TAG, "testInner is invoked:" + number);
        }
    }
}

1.3 注册方式

https://www.cnblogs.com/zl1991/p/6688914.html

1.3.1 静态注册

静态注册时jni定义的方法名称要跟java下调用jni代码的类名路径对应,比如当前项目TestJniActivity中调用jni方法,路径是src/main/java/com/example/chuckapptestdemo/TestJniActivity,可以将cmd定位到java这一层,通过执行:

javah -d jni com.example.chuckapptestdemo.TestJniActivity

在java目录下生成com_example_chuckapptestdemo_TestJniActivity.h文件,如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_chuckapptestdemo_TestJniActivity */

#ifndef _Included_com_example_chuckapptestdemo_TestJniActivity
#define _Included_com_example_chuckapptestdemo_TestJniActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_chuckapptestdemo_TestJniActivity
 * Method:    communicate2Jni
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_chuckapptestdemo_TestJniActivity_communicate2Jni
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

接下来在对应的c文件中include这个.h文件,并实现定义的方法就行了。

1.3.2 动态注册

在上一小节中,可以看到定义的方法名称都是跟java包下的类路径对应的,这样会导致定义的方法名称比较长。其实可以通过动态注册的方式解决这个问题。之前有分析过dart底层的代码,都是通过动态注册方法。

要使用 RegisterNatives,请执行以下操作:

  • 提供 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) 函数。
  • 在 JNI_OnLoad 中,使用 RegisterNatives 注册所有原生方法。
  • 使用 -fvisibility=hidden 进行构建,以便只从您的库中导出 JNI_OnLoad。这将生成速度更快且更小的代码,并避免与加载到应用中的其他库发生潜在冲突(但如果应用在原生代码中崩溃,则创建的堆栈轨迹的用处不大)。

看看动态注册的例子:

#include "DynamicRegister.h"

static JNINativeMethod activity_method_table[] = {
        {"communicate2Jni", "(Ljava/lang/String;)Ljava/lang/String;", (void *) communicate2Jni},
};

JNIEXPORT jstring JNICALL communicate2Jni(JNIEnv *env, jobject obj, jstring message) {
    const char *nativeString = (*env)->GetStringUTFChars(env, message, 0);
    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s", nativeString);
    (*env)->ReleaseStringUTFChars(env, message, nativeString);
    jstring response = (*env)->NewStringUTF(env, "jni say hi to java");
    return response;
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI_OnLoad");

    JNIEnv *env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }

    if (!registerNatives(env)) //注册
    {
        return -1;
    }

    result = JNI_VERSION_1_6;

    return result;
}

static int registerNatives(JNIEnv *env) {
    if (!registerNativeMethods(env, JNIREG_ACTIVITY_CLASS, activity_method_table,
                               sizeof(activity_method_table) / sizeof(activity_method_table[0]))) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI_OnUnload");
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI load GetEnv failed");
    else {
//        (*env)->DeleteGlobalRef(env, object);
//        (*env)->DeleteGlobalRef(env, object);
    }
}

动态注册方法时先将java层调用jni的方法名称与jni本地实际执行的方法对应起来,这里使用结构体JNINativeMethod,对应

sdk\ndk-bundle\sysroot\usr\include\jni.h

typedef struct {
    const char* name; //Java jni函数的名称
    const char* signature; //描述了函数的参数和返回值
    void* fnPtr; //函数指针,jni函数的对应的c函数
} JNINativeMethod;

上边两种注册方式对应在Activity中的代码是:

public class TestJniActivity extends Activity {

    final String TAG = "Communicate";

    static {
        System.loadLibrary("communicate");
    }
    
        @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_child_recycler_view);

        String response = communicate2Jni("say hello to jni");
        Log.d(TAG, "response:" + response);
    }

    public static native String communicate2Jni(String message);
}

以下内容来自
https://www.cnblogs.com/zl1991/p/6688914.html

当程序在java层运行System.loadLibrary(“communicate”);这行代码后,程序会去载入libcommunicate.so文件,与此同时,产生一个"Load"事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad函数并执行,与"Load"事件相对,当载入的.so文件被卸载时,"Unload"事件被触发,此时,程序默认会去在载入的.so文件的函数列表中查找JNI_OnUnload函数并执行,然后卸载.so文件。需要注意的是,JNI_OnLoad与JNI_OnUnload这两个函数在.so组件中并不是强制要求的,用户也可以不去实现,java代码一样可以调用到C组件中的函数。

动态注册方法的作用

应用层的Java类别通过VM而调用到native函数。一般是通过VM去寻找*.so里的native函数。如果需要连续调用很多次,每次都需要寻找一遍,会多花许多时间。

此时,C组件开发者可以将本地函数向VM进行注册,以便能加快后续调用native函数的效率。可以这么想象一下,假设VM内部一个native函数链表,初始时是空的,在未显式注册之前此native函数链表是空的,每次java调用native函数之前会首先在此链表中查找需要查找需要调用的native函数,如果找到就直接使用,如果未找到,得再通过载入的.so文件中的函数列表中去查找,且每次java调用native函数都是进行这样的流程,因此,效率就自然会下降,为了克服这样现象,我们可以通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VM的native函数链表中去,这样一来,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度.

java与jni的基本类型

很多文档都会有jni和java层的变量对应表,其实是来自于java的官方文档:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html

jni变量继承图

jni层变量类型与java的变量类型对应表:

Java TypeNative TypeDescription
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidnot applicable

变量签名对应的是:

Type SignatureJava Type
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[ typetype[]
( arg-types ) ret-typemethod type

例如java定义的方法是:

long f (int n, String s, int[] arr);

对应在jni层定义的签名对应的是:

(ILjava/lang/String;[I)J

括号里面对应的参数的签名,括号后面对应的是返回的数据类型。

1.4 jni的变量

https://developer.android.com/training/articles/perf-jni

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html

1.4.1 JavaVM 和 JNIEnv

JNI 定义了两个关键数据结构,即“JavaVM”和“JNIEnv”。两者本质上都是指向函数表的二级指针。(在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。)JavaVM 提供“调用接口”函数,您可以利用此类来函数创建和销毁 JavaVM。理论上,每个进程可以有多个 JavaVM,但 Android 只允许有一个。

JNIEnv 提供了大部分 JNI 函数。您的原生函数都会收到 JNIEnv 作为第一个参数。

该 JNIEnv 将用于线程本地存储。因此,您无法在线程之间共享 JNIEnv。如果一段代码无法通过其他方法获取自己的 JNIEnv,您应该共享相应 JavaVM,然后使用 GetEnv 发现线程的 JNIEnv。

1.4.2 局部引用和全局引用

传递给原生方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于“局部引用”。这意味着,局部引用在当前线程中的当前原生方法运行期间有效。 在原生方法返回后,即使对象本身继续存在,该引用也无效。

这适用于 jobject 的所有子类,包括 jclass、jstring 和 jarray。(启用扩展的 JNI 检查时,运行时会针对大部分引用误用问题向您发出警告。)

获取非局部引用的唯一方法是通过 NewGlobalRef 和 NewWeakGlobalRef 函数。

如果您希望长时间保留某个引用,则必须使用“全局”引用。NewGlobalRef 函数将局部引用作为参数,然后返回全局引用。在调用 DeleteGlobalRef 之前,全局引用保证有效。

所有 JNI 方法都接受局部和全局引用作为参数。对同一对象的引用可能具有不同的值。例如,对同一对象连续调用 NewGlobalRef 所返回的值可能有所不同。 要了解两个引用是否引用同一对象,必须使用 IsSameObject 函数。

程序员需要“不过度分配”局部引用。实际上,这意味着如果您要创建大量局部引用(也许是在运行对象数组时),应该使用 DeleteLocalRef 手动释放它们,而不是让 JNI 为您代劳。

该实现仅需要为 16 个局部引用保留槽位,因此如果您需要更多槽位,则应该按需删除,或使用 EnsureLocalCapacity/PushLocalFrame 保留更多槽位。

1.5 jni变量

以下内容引用自https://blog.csdn.net/xyang81/article/details/44657385

JNI定义了三种类型的变量,分别是:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。

1.5.1 局部引用

通过NewLocalRef和各种JNI接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等),会阻止GC回收所引用的对象,不在本地函数中跨函数使用,不能跨线程使用。函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef释放。

具体示例如下:

JNIEXPORT void JNICALL testObjectCreateAndRelease(JNIEnv *env, jobject obj) {
    jclass testclass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
    jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
    jobject testobject = (*env)->NewObject(env, testclass, testcontruct);
    jmethodID test = (*env)->GetMethodID(env, testclass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testobject, test, 1);
    (*env)->DeleteLocalRef(env, testobject);
    (*env)->DeleteLocalRef(env, testclass);
}

局部引用也称本地引用,通常是在函数中创建并使用。会阻止GC回收所引用的对象。比如,调用NewObject接口创建一个新的对象实例并返回一个对这个对象的局部引用。局部引用只有在创建它的本地方法返回前有效,本地方法返回到Java层之后,如果Java层没有对返回的局部引用使用的话,局部引用就会被JVM自动释放。

1.5.1.1 局部引用错误的使用方式

你可能会为了提高程序的性能,在函数中将局部引用存储在静态变量中缓存起来,供下次调用时使用。这种方式是错误的,因为函数返回后局部引很可能马上就会被释放掉,静态变量中存储的就是一个被释放后的内存地址,成了一个野针对,下次再使用的时候就会造成非法地址的访问,使程序崩溃。

错误的释放方法示例如下:

Activity中调用

public class TestJniActivity extends Activity {

    final String TAG = "Communicate";

    static {
        System.loadLibrary("communicate");
    }
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String testString = testMistakeLocalRefCache();
        Log.d(TAG, "testString index 0:" + testString);
        testString = testMistakeLocalRefCache();
        Log.d(TAG, "testString index 1:" + testString);
    }
    
    public static native String testMistakeLocalRefCache();
}

jni中代码实现如下:

JNIEXPORT jstring JNICALL testMistakeLocalRefCache(JNIEnv *env, jobject obj) {
    static jclass testclass;
    jstring j_str = NULL;
    if (testclass == NULL) {
        testclass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
        if (testclass == NULL) {
            return NULL;
        }
    }

    jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
    jobject testobject = (*env)->NewObject(env, testclass, testcontruct);

    jmethodID testReturnValue = (*env)->GetMethodID(env, testclass, "testReturnValue",
                                                    "(Ljava/lang/String;)V");
    j_str = (*env)->NewStringUTF(env, "testMistakeLocalRefCache");
    (*env)->CallVoidMethod(env, testobject, testReturnValue, j_str);

    (*env)->DeleteLocalRef(env, testobject);

    return j_str;
}

第一次调用完毕之后,testclass被释放;第二次再调用的时候,testclass本身不为空,但是指向为空,导致报错。

报错内容是:

JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x91
1.5.1.2 局部引用释放

释放一个局部引用有两种方式,一个是本地方法执行完毕后JVM自动释放,另外一个是自己调用DeleteLocalRef手动释放。既然JVM会在函数返回后会自动释放所有局部引用,为什么还需要手动释放呢?大部分情况下,我们在实现一个本地方法时不必担心局部引用的释放问题,函数被调用完成后,JVM 会自动释放函数中创建的所有局部引用。尽管如此,以下几种情况下,为了避免内存溢出,我们应该手动释放局部引用:

  • 1、不需要的局部引用应立即删除

JNI会将创建的局部引用都存储在一个局部引用表中,如果这个表超过了最大容量限制,就会造成局部引用表溢出,使程序崩溃。经测试,Android上的JNI局部引用表最大数量是512个。当我们在实现一个本地方法时,可能需要创建大量的局部引用,如果没有及时释放,就有可能导致JNI局部引用表的溢出,所以,在不需要局部引用时就立即调用DeleteLocalRef手动删除。

尤其是在循环里面创建局部引用时,应当在循环内使用完毕之后立即删除。

  • 2、工具类的函数中应当及时删除局部引用

在编写JNI工具函数时,工具函数在程序当中是公用的,被谁调用你是不知道的。上面newString这个函数演示了怎么样在工具函数中使用完局部引用后,调用DeleteLocalRef删除。不这样做的话,每次调用newString之后,都会遗留两个引用占用空间。

  • 3、消息循环中应当及时删除局部引用

如果你的本地函数不会返回。比如一个接收消息的函数,里面有一个死循环,用于等待别人发送消息过来,如下:

while(true) { 
    if (有新的消息) { 
        处理之。。。。
    } else { 
        等待新的消息。。。
    }
}

如果在消息循环当中创建的引用你不显示删除,很快将会造成JVM局部引用表溢出。

  • 4、函数开始分配的局部引用应当及早释放

局部引用会阻止所引用的对象被GC回收。比如你写的一个本地函数中刚开始需要访问一个大对象,因此一开始就创建了一个对这个对象的引用,但在函数返回前会有一个大量的非常复杂的计算过程,而在这个计算过程当中是不需要前面创建的那个大对象的引用的。但是,在计算的过程当中,如果这个大对象的引用还没有被释放的话,会阻止GC回收这个对象,内存一直占用者,造成资源的浪费。所以这种情况下,在进行复杂计算之前就应该把引用给释放了,以免不必要的资源浪费。

1.5.1.3 管理局部引用

JNI提供了一系列函数来管理局部引用的生命周期。这些函数包括:EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame、DeleteLocalRef。JNI规范指出,任何实现JNI规范的JVM,必须确保每个本地函数至少可以创建16个局部引用(可以理解为虚拟机默认支持创建16个局部引用)。

  • EnsureLocalCapacity

可以通过调用EnsureLocalCapacity函数,确保在当前线程中创建指定数量的局部引用,如果创建成功则返回0,否则创建失败,并抛出OutOfMemoryError异常。

如下:

JNIEXPORT jstring JNICALL testLocalRefCapacity(JNIEnv *env, jobject obj, jcharArray charArray) {
    LOGE("testLocalRefCapacity start");
    jsize size = (*env)->GetArrayLength(env, charArray);
    if (size == 0) {
        return (*env)->NewStringUTF(env, "size is error");
    }
    LOGE("testLocalRefCapacity size:%d", size);
    if ((*env)->EnsureLocalCapacity(env, size) != 0) {
        return (*env)->NewStringUTF(env, "memory size is invalid");
    }
    jchar *jcharPointer = (*env)->GetCharArrayElements(env, charArray, NULL);
    if (jcharPointer == NULL) {
        return (*env)->NewStringUTF(env, "jchar pointer is error");
    }
    int index = 0;
    for (; index < size; index++) {
        const jchar *jcharItem = (jcharPointer + index);
        LOGE("testLocalRefCapacity current char:%s",
             jcharItem);
        jstring str = (*env)->NewStringUTF(env, jcharItem);
        (*env)->DeleteLocalRef(env, str);
    }
    (*env)->ReleaseCharArrayElements(env, charArray, jcharPointer, 0);//mode,0 for copy back the content and free the elems buffer
    return (*env)->NewStringUTF(env, "testLocalRefCapacity success");
}
  • Push/PopLocalFrame

除了EnsureLocalCapacity函数可以扩充指定容量的局部引用数量外,我们也可以利用Push/PopLocalFrame函数对创建作用范围层层嵌套的局部引用。例如,我们把上面那段处理字符串数组的代码用Push/PopLocalFrame函数对重写:

JNIEXPORT jstring JNICALL testLocalRefFrame(JNIEnv *env, jobject obj, jcharArray charArray) {
    LOGE("testLocalRefFrame start");
    jsize size = (*env)->GetArrayLength(env, charArray);
    if (size == 0) {
        return (*env)->NewStringUTF(env, "size is error");
    }
    LOGE("testLocalRefFrame size:%d", size);
    if ((*env)->EnsureLocalCapacity(env, size) != 0) {
        return (*env)->NewStringUTF(env, "memory size is invalid");
    }
    jchar *jcharPointer = (*env)->GetCharArrayElements(env, charArray, NULL);
    if (jcharPointer == NULL) {
        return (*env)->NewStringUTF(env, "jchar pointer is error");
    }
    int index = 0;
    for (; index < size; index++) {
        if ((*env)->PushLocalFrame(env, N_REFS) != 0) {
            return (*env)->NewStringUTF(env, "ref size over flow");
        }
        const jchar *jcharItem = (jcharPointer + index);
        LOGE("testLocalRefFrame current char:%s",
             jcharItem);
        jstring str = (*env)->NewStringUTF(env, jcharItem);
        (*env)->PopLocalFrame(env, NULL);
    }
    (*env)->ReleaseCharArrayElements(env, charArray, jcharPointer,
                                     0);//mode,0 for copy back the content and free the elems buffer
    return (*env)->NewStringUTF(env, "testLocalRefFrame success");
}

PushLocalFrame为当前函数中需要用到的局部引用创建了一个引用堆栈,(如果之前调用PushLocalFrame已经创建了Frame,在当前的本地引用栈中仍然是有效的)每遍历一次调用(*env)->GetObjectArrayElement(env, arr, i);返回一个局部引用时,JVM会自动将该引用压入当前局部引用栈中。而PopLocalFrame负责销毁栈中所有的引用,这样一来,Push/PopLocalFrame函数对提供了对局部引用生命周期更方便的管理,而不需要时刻关注获取一个引用后,再调用DeleteLocalRef来释放引用。在上面的例子中,如果在处理jstr的过程当中又创建了局部引用,则PopLocalFrame执行时,这些局部引用将全都会被销毁。在调用PopLocalFrame销毁当前frame中的所有引用前,如果第二个参数result不为空,会由result生成一个新的局部引用,再把这个新生成的局部引用存储在上一个frame中。

局部引用不能跨线程使用,只在创建它的线程有效。不要试图在一个线程中创建局部引用并存储到全局引用中,然后在另外一个线程中使用。

1.5.2 全局引用

调用NewGlobalRef基于局部引用创建,会阻GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放(*env)->DeleteGlobalRef(env,g_cls_string);

JNIEXPORT jstring JNICALL testGlobalRef(JNIEnv *env, jobject obj, jint number) {
    LOGE("testGlobalRef number %d", number);
    static jclass testClass;
    if (testClass == NULL) {
        LOGE("testGlobalRef first time testClass == NULL");
        jclass curTestClass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
        testClass = (*env)->NewGlobalRef(env, curTestClass);
        (*env)->DeleteLocalRef(env, curTestClass);
    } else {
        LOGE("testGlobalRef second time testClass != NULL");
    }

    jmethodID testConstruct = (*env)->GetMethodID(env, testClass, "<init>", "()V");
    jobject testObject = (*env)->NewObject(env, testClass, testConstruct);
    jmethodID test = (*env)->GetMethodID(env, testClass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testObject, test, 1);
    return (*env)->NewStringUTF(env, "testGlobalRef success");
}
1.5.2.1 管理全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,也会阻止它所引用的对象被GC回收。与局部引用创建方式不同的是,只能通过NewGlobalRef函数创建。

当我们的本地代码不再需要一个全局引用时,应该马上调用DeleteGlobalRef来释放它。如果不手动调用这个函数,即使这个对象已经没用了,JVM也不会回收这个全局引用所指向的对象。

1.5.3 弱全局引用

调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef手动释放。(*env)->DeleteWeakGlobalRef(env,g_cls_string)

JNIEXPORT jstring JNICALL testWeakGlobalRef(JNIEnv *env, jobject obj, jint number) {
    LOGE("testWeakGlobalRef number %d", number);
    static jclass weakTestClass;
    if (weakTestClass == NULL) {
        LOGE("testWeakGlobalRef first time testClass == NULL");
        jclass curTestClass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
        weakTestClass = (*env)->NewWeakGlobalRef(env, curTestClass);
        (*env)->DeleteLocalRef(env, curTestClass);
    } else {
        LOGE("testWeakGlobalRef second time testClass != NULL");
    }

    jmethodID testConstruct = (*env)->GetMethodID(env, weakTestClass, "<init>", "()V");
    jobject testObject = (*env)->NewObject(env, weakTestClass, testConstruct);
    jmethodID test = (*env)->GetMethodID(env, weakTestClass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testObject, test, 1);
    return (*env)->NewStringUTF(env, "testWeakGlobalRef success");
}
1.5.3.1 管理弱全局引用

弱全局引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。下面简称弱引用。与全局引用类似,弱引用可以跨方法、线程使用。但与全局引用很重要不同的一点是,弱引用不会阻止GC回收它引用的对象。

当我们的本地代码不再需要一个弱全局引用时,也应该调用DeleteWeakGlobalRef来释放它,如果不手动调用这个函数来释放所指向的对象,JVM仍会回收弱引用所指向的对象,但弱引用本身在引用表中所占的内存永远也不会被回收。

1.5.4 引用比较

给定两个引用(不管是全局、局部还是弱全局引用),我们只需要调用IsSameObject来判断它们两个是否指向相同的对象。例如:(*env)->IsSameObject(env, obj1, obj2),如果obj1和obj2指向相同的对象,则返回JNI_TRUE(或者1),否则返回JNI_FALSE(或者0)。有一个特殊的引用需要注意:NULL,JNI中的NULL引用指向JVM中的null对象。如果obj是一个局部或全局引用,使用(*env)->IsSameObject(env, obj, NULL) 或者 obj == NULL 来判断obj是否指向一个null对象即可。

但需要注意的是,IsSameObject用于弱全局引用与NULL比较时,返回值的意义是不同于局部引用和全局引用的:

JNIEXPORT jstring JNICALL testSameObject(JNIEnv *env, jobject obj, jint number) {
    jclass curTestClass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
    jmethodID testConstruct = (*env)->GetMethodID(env, curTestClass, "<init>", "()V");
    jobject testObject = (*env)->NewObject(env, curTestClass, testConstruct);

    jclass weakGlobalTestObject = (*env)->NewWeakGlobalRef(env, testObject);
    jclass globalTestObject = (*env)->NewGlobalRef(env, testObject);

    jboolean isGlobalObjectEqual = (*env)->IsSameObject(env, testObject, weakGlobalTestObject);
    jboolean isWeakGlobalObjectEqual = (*env)->IsSameObject(env, testObject, globalTestObject);

    LOGE("testSameObject isGlobalObjectEqual %d", isGlobalObjectEqual);
    LOGE("testSameObject isWeakGlobalObjectEqual %d", isWeakGlobalObjectEqual);

//    (*env)->DeleteWeakGlobalRef(env, weakGlobalTestObject);
    jboolean isWeakGlobalObjectNull = (*env)->IsSameObject(env, weakGlobalTestObject, NULL);
    LOGE("testSameObject isWeakGlobalObjectNull %d", isWeakGlobalObjectNull);
    return (*env)->NewStringUTF(env, "testSameObject success");
}

在上面的IsSameObject调用中,如果weakGlobalTestObject指向的引用testObject已经被回收,会返回JNI_TRUE,否则会返回JNI_FALSE。

1.6 jni崩溃排查

报错代码如下:

JNIEXPORT void JNICALL testError(JNIEnv *env, jobject obj) {
    jclass curTestClass = (*env)->FindClass(env, "com/example/chuckapptestdemo/TestBeInvoked");
    jmethodID testConstruct = (*env)->GetMethodID(env, curTestClass, "<init>", "()V");
    jobject testObject = (*env)->NewObject(env, curTestClass, testConstruct);

    (*env)->DeleteLocalRef(env, testObject);

    jmethodID test = (*env)->GetMethodID(env, curTestClass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testObject, test, 1);
}

报错信息如下:

2020-07-03 11:29:18.808 12279-12279/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #09 pc 0000000000100a40  /system/lib64/libart.so (art::CheckJNI::CallVoidMethod(_JNIEnv*, _jobject*, _jmethodID*, ...)+156)
2020-07-03 11:29:18.808 12279-12279/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #10 pc 0000000000001c18  /data/app/com.example.chuckapptestdemo-xS2K0ByIriN_rE4Udf-ufw==/lib/arm64/libcommunicate.so (testError+280)
2020-07-03 11:29:18.808 12279-12279/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #11 pc 00000000000001e0  /data/app/com.example.chuckapptestdemo-xS2K0ByIriN_rE4Udf-ufw==/oat/arm64/base.odex (Java_com_example_chuckapptestdemo_TestJniActivity_testError__+144)
2020-07-03 11:29:18.808 12279-12279/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   at com.example.chuckapptestdemo.TestJniActivity.testError(Native method)
1.6.1 addr2line

首先需要在sdk manager中安装ndk。

查看测试机器的CPU架构:adb shell cat /proc/cpuinfo,输出:

Processor       : AArch64 Processor rev 4 (aarch64)
processor       : 0
BogoMIPS        : 38.40
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x51
CPU architecture: 8
CPU variant     : 0xa
CPU part        : 0x801
CPU revision    : 4

AArch 64位机器。

ndk安装完成之后,根据设备CPU架构,找到addr2line在ndk目录下的地址。以当前测试机器的架构为例,对应的工具地址是:D:\android-adt\sdk\ndk-bundle\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-addr2line.exe

找到本地生成的so库在项目下的路径,当前测试项目的路径是:E:\android\workspace\Dev\ChuckAppTestDemo\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a\libcommunicate.so

执行指令:
arm-linux-androideabi-addr2line.exe -e E:\android\workspace\Dev\ChuckAppTestDemo\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a\libcommunicate.so 0000000000100a40 0000000000001c18 00000000000001e0

输出:

??
??:0
testError
E:/android/workspace/Dev/ChuckAppTestDemo/app/src/main/java/jni/DynamicRegister.c:186
??
??:0

对于这些??可以试着更新ndk版本解决。

另外如果不知道addr2line后跟的参数含义,可以执行aarch64-linux-android-addr2line.exe -h,输出如下:

Usage: aarch64-linux-android-addr2line.exe [option(s)] [addr(s)]
 Convert addresses into line number/file name pairs.
 If no addresses are specified on the command line, they will be read from stdin
 The options are:
  @<file>                Read options from <file>
  -a --addresses         Show addresses
  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -p --pretty-print      Make the output easier to read for humans
  -s --basenames         Strip directory names
  -f --functions         Show function names
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version

aarch64-linux-android-addr2line.exe: supported targets: elf64-littleaarch64 elf64-bigaarch64 elf32-littleaarch64 elf32-bigaarch64 elf32-littlearm elf32-bigarm elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
Report bugs to <http://www.sourceware.org/bugzilla/>
1.6.2 ndk-stack

以下内容参考:https://developer.android.com/ndk/guides/ndk-stack

第一步,在下载的ndk目录下找到ndk-stack.cmd。

第二步,执行指令:adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi-v7a

| 后面过滤的内容分别是:$NDK/ndk-stack表示ndk-stack所在的路径;-sym后边是项目生成的so地址,对应当前测试项目的执行指令是:
adb logcat | ndk-stack.cmd -sym E:\android\workspace\Dev\ChuckAppTestDemo\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a

第三步,运行程序,当运行5.6.1中的错误代码后,在cmd输出:

********** Crash dump: **********
Build fingerprint: '360/QK1807/QK1807:8.1.0/OPM1/8.1.109.PX.181221.360OS_360OS_QK1807_CN:user/release-keys'
pid: 15437, tid: 15437, name: huckapptestdemo  >>> com.example.chuckapptestdemo <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #00 pc 000000000001dec8  /system/lib64/libc.so (abort+104)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #01 pc 000000000047523c  /system/lib64/libart.so (art::Runtime::Abort(char const*)+552)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #02 pc 0000000000571a00  /system/lib64/libart.so (android::base::LogMessage::~LogMessage()+996)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #03 pc 00000000002ff090  /system/lib64/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1700)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #04 pc 00000000002ff2e4  /system/lib64/libart.so (art::JavaVMExt::JniAbortF(char const*, char const*, ...)+180)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #05 pc 00000000004a1568  /system/lib64/libart.so (art::Thread::DecodeJObject(_jobject*) const+464)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #06 pc 000000000010f074  /system/lib64/libart.so (art::ScopedCheck::CheckInstance(art::ScopedObjectAccess&, art::ScopedCheck::InstanceKind, _jobject*, bool)+96)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #07 pc 000000000010dcd8  /system/lib64/libart.so (art::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::JniValueType*)+644)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #08 pc 0000000000113154  /system/lib64/libart.so (art::CheckJNI::CheckCallArgs(art::ScopedObjectAccess&, art::ScopedCheck&, _JNIEnv*, _jobject*, _jclass*, _jmethodID*, art::InvokeType, art::VarArgs const*)+132)
Stack frame 07-03 14:34:37.991 15478 15478 F DEBUG   :     #09 pc 000000000011200c  /system/lib64/libart.so (art::CheckJNI::CallMethodV(char const*, _JNIEnv*, _jobject*, _jclass*, _jmethodID*, std::__va_list, art::Primitive::Type, art::InvokeType)+664)
Stack frame 07-03 14:34:37.992 15478 15478 F DEBUG   :     #10 pc 0000000000100a40  /system/lib64/libart.so (art::CheckJNI::CallVoidMethod(_JNIEnv*, _jobject*, _jmethodID*, ...)+156)
Stack frame 07-03 14:34:37.992 15478 15478 F DEBUG   :     #11 pc 0000000000001c18  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/lib/arm64/libcommunicate.so (testError+280): Routine testError at E:/android/workspace/Dev/ChuckAppTestDemo/app/src/main/java/jni/DynamicRegister.c:186
Stack frame 07-03 14:34:37.992 15478 15478 F DEBUG   :     #12 pc 000000000001d1e0  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/oat/arm64/base.odex (offset 0x1d000)

这里就可以定位到DynamicRegister.c:186

也可以使用 -dump 选项将 logcat 指定为输入文件。例如:
adb logcat > /tmp/foo.txt

$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi-v7a -dump foo.txt

对应当前测试项目的指令是:

运行存在报错的代码,将崩溃的日志输出到本地磁盘:
adb logcat > D:\log\foo.txt

接下来,运用ndk-stack解析foo.txt文件中的日志,该工具会在开始解析 logcat 输出时查找第一行星号。执行指令:
ndk-stack.cmd -sym E:\android\workspace\Dev\ChuckAppTestDemo\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a -dump D:\log\foo.txt

输出如下:

********** Crash dump: **********
Build fingerprint: '360/QK1807/QK1807:8.1.0/OPM1/8.1.109.PX.181221.360OS_360OS_QK1807_CN:user/release-keys'
pid: 15692, tid: 15692, name: huckapptestdemo  >>> com.example.chuckapptestdemo <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #00 pc 000000000001dec8  /system/lib64/libc.so (abort+104)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #01 pc 000000000047523c  /system/lib64/libart.so (art::Runtime::Abort(char const*)+552)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #02 pc 0000000000571a00  /system/lib64/libart.so (android::base::LogMessage::~LogMessage()+996)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #03 pc 00000000002ff090  /system/lib64/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1700)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #04 pc 00000000002ff2e4  /system/lib64/libart.so (art::JavaVMExt::JniAbortF(char const*, char const*, ...)+180)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #05 pc 00000000004a1568  /system/lib64/libart.so (art::Thread::DecodeJObject(_jobject*) const+464)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #06 pc 000000000010f074  /system/lib64/libart.so (art::ScopedCheck::CheckInstance(art::ScopedObjectAccess&, art::ScopedCheck::InstanceKind, _jobject*, bool)+96)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #07 pc 000000000010dcd8  /system/lib64/libart.so (art::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::JniValueType*)+644)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #08 pc 0000000000113154  /system/lib64/libart.so (art::CheckJNI::CheckCallArgs(art::ScopedObjectAccess&, art::ScopedCheck&, _JNIEnv*, _jobject*, _jclass*, _jmethodID*, art::InvokeType, art::VarArgs const*)+132)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #09 pc 000000000011200c  /system/lib64/libart.so (art::CheckJNI::CallMethodV(char const*, _JNIEnv*, _jobject*, _jclass*, _jmethodID*, std::__va_list, art::Primitive::Type, art::InvokeType)+664)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #10 pc 0000000000100a40  /system/lib64/libart.so (art::CheckJNI::CallVoidMethod(_JNIEnv*, _jobject*, _jmethodID*, ...)+156)
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #11 pc 0000000000001c18  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/lib/arm64/libcommunicate.so (testError+280): Routine testError at E:/android/workspace/Dev/ChuckAppTestDemo/app/src/main/java/jni/DynamicRegister.c:186
Stack frame 07-03 14:44:12.092 15729 15729 F DEBUG   :     #12 pc 000000000001d1e0  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/oat/arm64/base.odex (offset 0x1d000)

这样也可以定位到。

同样的,可以通过-h查看后面追加参数的含义:

D:\android-adt\sdk\ndk-bundle>ndk-stack.cmd -h
Usage: ndk-stack -sym PATH [-dump PATH]
Symbolizes the stack trace from an Android native crash.

  -sym PATH   sets the root directory for symbols
  -dump PATH  sets the file containing the crash dump (default stdin)

See <https://developer.android.com/ndk/guides/ndk-stack.html>.
1.6.3 objdump

当前报错的堆栈是:

2020-07-03 14:44:11.990 15692-15692/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #09 pc 0000000000100a40  /system/lib64/libart.so (art::CheckJNI::CallVoidMethod(_JNIEnv*, _jobject*, _jmethodID*, ...)+156)
2020-07-03 14:44:11.990 15692-15692/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #10 pc 0000000000001c18  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/lib/arm64/libcommunicate.so (testError+280)
2020-07-03 14:44:11.990 15692-15692/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   native: #11 pc 00000000000001e0  /data/app/com.example.chuckapptestdemo-DcWQH_KnoZj9ObTNecX4-w==/oat/arm64/base.odex (Java_com_example_chuckapptestdemo_TestJniActivity_testError__+144)
2020-07-03 14:44:11.990 15692-15692/com.example.chuckapptestdemo A/zygote64: java_vm_ext.cc:534]   at com.example.chuckapptestdemo.TestJniActivity.testError(Native method)

5.6.1步骤类似,根据机器CPU架构找到objdump所在PC的路径:
D:\android-adt\sdk\ndk-bundle\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-objdump.exe

接下里执行指令:
aarch64-linux-android-objdump.exe -S -D E:\android\workspace\Dev\ChuckAppTestDemo\app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a\libcommunicate.so > D:\log\error.dump

-D:后面跟so库在项目中的地址。

这个指令的主要任务是将jni中的方法转成内存地址的形式转存到本地磁盘文件中。

对应报错的内存地址是0000000000001c18,在输出的error.dump文件中找到内存地址对应的方法:

JNIEXPORT void JNICALL testError(JNIEnv *env, jobject obj) {
    (*env)->CallVoidMethod(env, testObject, test, 1);
    1bfc:	00074d19 	.word	0x00074d19
    1c00:	00b81900 	.word	0x00b81900
    1c04:	ad190000 	.word	0xad190000
    1c08:	19000000 	.word	0x19000000
    1c0c:	0000145d 	.word	0x0000145d
    1c10:	170d001c 	.word	0x170d001c
    1c14:	1800001c 	.word	0x1800001c
    1c18:	000014bb 	.word	0x000014bb
}

可以定位到testError的最后一行报错。还有其他的内存地址都可以在这个error.dump文件中找到。

1.7 jni崩溃catch

https://github.com/xroche/coffeecatch

https://stackoverflow.com/questions/33777370/android-jni-exception-handling

https://stackoverflow.com/questions/8415032/catching-exceptions-thrown-from-native-code-running-on-android

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值