Android JNI概述

本文基于Android 9.0源码分析

Android JNI简介

JNI是Java Native Interface, 它提供了一种从字节码(Java/Kotlin)到Native代码(c/c++/assembly)的交互方式

JavaVM与JNIEnv

JNI定义了两个关键的数据结构:JavaVM和JNIEnv

  • JavaVM
    • JavaVM提供了"invocation interface"函数表,允许你创建和销毁JavaVM,理论上你可以在进程内创建多个JavaVM实例,但Android只允许创建一个。
  • JNIEnv
    • JNIEnv提供了大部分JNI方法,JNIEnv存储在TLS中,基于上述原因,不能在线程间共享JNIEnv。上面的图中JNINativeInterface实际是全局结构体,图有点问题。
  • 线程
    • Android中的线程都是Linux线程,由Linux内核调度。通常通过Thread.start()启动线程,但是也可以使用其他方法启动线程,然后attach到JavaVM。比如,使用pthread_create()创建线程,调用AttachCurrentThread()或者AttachCurrentThreadAsDaemon()attach到JavaVM。注意,在attach之前,没有JNIEnv,无法使用JNI。

Android Native方法注册

使用RegisterNatives显示注册

示例

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    ......
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
// 1) using RegisterNatives register native method
static jstring StringFromJNI(JNIEnv *env, jobject obj) {
  std::string hello = "Hello from C++";
  return env->NewStringUTF(hello.c_str());
}

static JNINativeMethod jni_methods[] = {
    {"stringFromJNI", "()Ljava/lang/String;", (void *)StringFromJNI}
};

// 这里JNI_OnLoad不需要添加extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  JNIEnv* env;

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

  if (env->RegisterNatives(env->FindClass("lbtrace/jniregister/MainActivity"),
      jni_methods, sizeof(jni_methods) / sizeof(JNINativeMethod)) != JNI_OK)
    return -1;

  return JNI_VERSION_1_6;
}

原理

  • 首先获取调用者的类加载(这里是应用类加载器),然后在类加载器的DexPathList中查找Native库

    • 根据Native库的名字产生平台相关的库名
    • 在DexPathList的Native库路径列表中查找Native库,找到后返回库的绝对路径
  • JavaVM负责加载Native库,如果没有加载过,装载Native库到进程地址空间,然后查找Native库中是否有函数JNI_OnLoad(),如果存在,执行;否则运行时动态查找Native方法。

  • JNI_OnLoad()中,首先获取当前线程的JNIEnv,然后调用其RegisterNatives()注册Native方法。

    • 首先检查需要注册Native方法的信息是否合法
    • 根据方法名、方法签名查找对应的Native方法的ArtMethod
    • 将Native函数地址写入ArtMethod对象特定的成员中

运行时动态查找

示例

// 2) Android Runtime dynamic find native method
// 必须添加extern "C"
extern "C" JNIEXPORT jstring JNICALL
Java_lbtrace_jniregister_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

原理

  • ART运行时执行到调用Native方法的字节码时,首先获取方法对应的ArtMethod,根据ArtMethod对象的地址以及成员entry_point_from_quick_compiled_code_的偏移得到JNI Stub的地址。
  • 在JNI Stub函数中,首先进行Native方法调用前的准备工作(参数处理、进程状态切换等),然后根据特定的Native方法名(java_xxx_xxx_xxx)查找Native方法,调用Native方法,Native方法地址保存到ArtMethod,最后是Native方法调用结束后的清理工作。

思考:Native方法中jobject类型的参数是什么?

在先前的Android版本中是Local Reference

  • StackReference

Android JNI本地引用和全局引用

对于基本类型,进行JNI调用时,可以直接拷贝到Native方法,但是对于Java对象采用引用传递。虚拟机必须能够追踪到所有传递到Native方法的Java对象,避免被GC回收。当Native方法不再需要Java对象时,必须有一种方法通知虚拟机。

对于本地引用及全局引用的实现细节,将在后续文章中讨论。

Local Reference

本地引用在Native方法调用期间可用,在Native方法返回后自动释放。

  • 对于大的Java对象的本地引用,不用时应及时释放
  • 不要创建太多本地应用

Global Reference

全局引用不会自动释放,直到显式的删除

Weak Global Reference

弱全局引用是一种特殊的全局引用,与普通的全局引用不同的是,GC可以回收弱全局引用关联的Java对象。当GC运行时,会回收一个仅仅被弱全局引用关联Java对象。所以在使用之前,应该首先检查弱全局引用的Java对象是否被回收。

思考:Android中Java引用到底是什么?

  • 根据Java引用原理,可以在Native层直接修改Java对象内容
public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        TestObject testObject = new TestObject();
        Log.i("JNI", "Before : " + testObject.month + "月" +
                testObject.day + "日");
        stringFromJNI(testObject);
        Log.i("JNI", "After : " + testObject.month + "月" +
                testObject.day + "日");
        ......
    }

    public native String stringFromJNI(Object obj);

    static class TestObject {
        int month = 11;
        int day = 29;
    }
}

extern "C" JNIEXPORT jstring JNICALL
Java_lbtrace_jniregister_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject obj, jobject test_obj) {
    std::string hello = "Hello from C++";

    // Java TestObject Mirror address
    int32_t *test_obj_ptr = reinterpret_cast<int32_t *>(
        *(reinterpret_cast<int32_t *>(test_obj)));

    // According TestObject memory layout
    // Swap month field and day field in TestObject
    int32_t tmp = *(test_obj_ptr + 2);
    *(test_obj_ptr + 2) = *(test_obj_ptr + 3);
    *(test_obj_ptr + 3) = tmp;

    return env->NewStringUTF(hello.c_str());
}

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 闲来翻译了一篇官方的JNI Tips,网上看到的翻译版本要么是时间久了不同步了,要么翻译的过于生硬,看得我怀疑自己...
    生活简单些阅读 1,711评论 1 4
  • 本系列文章如下: Android JNI(一)——NDK与JNI基础Android JNI学习(二)——实战JNI...
    隔壁老李头阅读 204,099评论 25 533
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,652评论 2 59
  • 在分析IPC基于Android 6.0)的过程中,里面的核心部分是Native的,并且还包含一些linux ker...
    Sophia_dd35阅读 3,309评论 0 7
  • 准备明天的考试。 难过。 丑陋似乎变成了一种原罪。 从来就没有过自信。 也没努力过,小时候似乎有一点打扮,照照镜子...
    听雷雷说阅读 117评论 0 0