JNI使用手册,其三,拾遗篇。

Some Tips

性能技巧

  • 缓存常用的Class、FieldID、MethoidID;
    • 缓存字段可能就有20+倍的差异(取决具体机型);
  • 避免复制整个数组;
    • GetTypeArrayRegion()和SetTypeArrayRegion()方法可以避免复制整个数组;
  • 用参数列表传递多个字段,比传递一个包含多个字段的对象性能要高;
    • 尽管传递一个包含多个字段的对象,更符合面向对象设计,且无需修改签名;
  • 尽量减少跨层调用;
    • 相比相同层的调用更为耗时;

内存泄漏

  • Java Heap的内存泄漏

    • 大量Java对象同时存在,Java Heap容量扩充到上限;
    • 程序错误;
  • native memory的内存泄漏

    • JVM中,Java Heap外的内存空间都是native memory;
    • 过多线程被创建;
    • Native语言使用不当,没有遵循其内存管理机制;
    • 局部引用和全局引用没有正确释放;

调用性能

测试项(1亿次) 华为荣耀7i 一加5
Java层调用JNI空方法 100501 ms 56102ms
JNI层调用Java空方法 167847 ms 44559ms
Java层调用本层空方法 62529 ms 1118ms
JNI层调用本层空方法 1071 ms 345ms
  • 似乎一加有做了一些优化,jni回调java层空方法耗时居然更小;
  • 前两项耗时可能有4000左右的波动,仅供参考;
  • 后两项耗时较为稳定,不同机型差异较为明显;

库文件加载

  • 调用native方法之前,需要调用System.loadLibrary()加载动态链接库;
  • Linux系统一般后缀为so,Windows系统为dll,系统根据平台拓展成真实的动态库文件名;
  • Android系统中,定向查找/data/app/${PackageName}/lib/${cpu平台}/libxxx.so

UTF-8编码

  • UTF-16
    • Java层的字符编码方式;
    • 但Java中和UTF-8沾边的字符串操作,都是使用标准的UTF-8;
  • MUTF-8(Modified UTF-8);
    • \u000表示为0xc0 0x80
    • JNI层的字符编码方式;
      • 传递给NewStringUTF的数据必须是MUTF-8;
      • GetStringChars不需要拷贝,GetStringUTFChars需要分配并转为MUTF-8;
      • Get获取的字符串jchar*jbyte*,都是指向原始数据类型的C指针,不是局部引用,需要手动释放;
  • UTF-8编码
    • \u000表示为0x00
    • 主要使用于C语言;

版本兼容性

  • 指定targetsdk,尽可能高;
  • 指定minsdk,最好和NDK中的android-target保持一致;
    • NDK中未使用高版本的API,但指定了较高的targetsdk,编译时将链接到较高版本;
    • 较高版本的部分API实现存在不一致;
      • 如signal.h中的signal函数,使用android-21在低于5.0版本系统无法编译运行,错误是无法找到signal函数;

Some Questions

FindClass调用失败

  • 多线程情景中
    • 从Java层调用Native层,FindClass获取正常;
      • 这种调用会携带栈帧信息,包括该类的Class Loader等;
    • 在Native层创建的线程中调用FindClass可能会失败,返回空值;
      • 解决方案一,缓存Class Loader;
      • 解决方案二,在JNI_OnLoad中初始缓存各个Class;

JNI_OnLoad()与JNI_OnUnload()

  • 当Android平台的VM执行System.loadLibrary(),总是先执行JNI_OnLoad()函数;
  • 在Android平台,JNI_OnUnload()函数不会被调用,没错,它不会被调用!!;

参考