首页
社区
课程
招聘
[原创] JNI基础学习笔记
发表于: 2020-5-23 17:34 7159

[原创] JNI基础学习笔记

2020-5-23 17:34
7159

源码版本:2.3.x
学习JNI实例:MediaScanner
MediaScanner的功能是扫描媒体文件并将它们存储到媒体数据库中,供其他程序使用

--> MediaScanner.java

--> android_media_MediaScanner.cpp

但是如何确定 Java 层的 native_init 函数对应的就是 JNI 层的 android_media_MediaScanner_native_init 函数。这就涉及到 JNI 函数的注册问题。

所谓 注册 就是将 Java 层声明的 Native 函数与 JNI 层对应的实现函数关联起来。在这个例子中, native_init 函数位于 android.media 包中,全路径名为 android.media.MediaScanner.native_intJNI 层函数的名字是 android_media_MediaScanner_native_initJNI 函数的注册方法分为静态注册动态注册

使用 Java 的工具程序 javah ,如 javah -o output packagename.classnamepackagename.classnameJava 代码编译后的 class 文件,而在生成的 output.h 文件里,声明了对应的 JNI 层函数,只要实现里面的函数即可。
--> androyid_media_MediaScanner.h::样例文件

缺点:

动态注册
JNI 技术中,有 JNINativeMethod 结构体用来记录 native 函数和 JNI 函数的对应关系。

MediaScanner JNI 层是这么做的~
--> android_media_MediaScanner.cpp

接下来是 registerNativeMethods 的实现
--> AndroidRuntime.cpp

jniRegisterNativeMethodsAndroid 平台为了方便 JNI 使用而提供的帮助函数。
--> JNIHelp.c

真是多重调用...
android_media_MediaScanner::register_android_media_MediaScanner -> AndroidRuntime::registerNativeMethods -> JNIHelp::jniRegisterNativeMethods
但说到最后也就 jniRegisterNativeMethods 中的两步

那么是什么时候完成注册呢?当 Java 层通过 System.loadLibray 加载完 JNI 动态库后,紧接着会查找 JNI_OnLoad 函数,如果有,就调用并进行注册。libmedia_jni.soJNI_OnLoad 函数我们可以在 android_media_MediaPlayer.cpp 中找到。
--> android_media_MediaPlayer.cpp

这里没什么花里胡哨的,就两个表格
基本数据类型
引用类型
可以看到除了 Java 中基本数据类型的数组、ClassStringThrowable外,其余所有的 Java 对象的数据类型在 JNI中用 jobject表示。

JNIEnv 是一个与线程相关的代表JNI环境的结构体。
JNIEnv内部结构
通过调用JNIEnv的一些JNI系统函数进而可以调用 Java 的函数、操作 jobject 对象等很多事情。

操作 jobject 即是操作该对象的成员变量和成员函数,在 JNI 规则中,用 jfieldIDjmethodID 表示。

MS中这样使用它们
--> android_media_MediaScanner.cpp::MyMediaScannerClient构造函数
--> android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile

Java 支持函数重载,即可以定义同名但不同参数的函数。仅仅通过函数名无法找到具体函数。为了解决这个问题,JNI 技术中将参数类型和返回值类型的组合作为了一个函数的签名信息,有了签名信息和函数名,就能很顺利的找到 Java 中的函数。
格式: ( 参数 1 类型标示 参数 2 类型标示 ... 参数 n 类型标示 ) 返回值类型标示
举例: ( Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient; )V

类型标示示意表

Java 中创建的对象最后由垃圾回收器回收和释放内存。
--> 垃圾回收的例子

这个做法会有问题,因为和 save_thiz 对应的 java 层中的 MediaScanner 很有可能已经被垃圾回收了,即 save_thiz 保存的 jobject 可能是个野指针,如果使用它,后果会很严重。在被引用中被清理的原因是: JNI 层使用 save_thiz = thiz 这种语句是不会增加 jobject 的引用计数的。
为此, JNI 提供了三种类型的引用:

--> android_media_MediaScanner.cpp::MyMediaScannerClient构造函数

调用 JNIEnv 的某些函数出错,会产生异常,但直到返回到 Java 层后才会抛出。异常不会中断本地函数的运行,但是只能做资源清理的工作,如果此时调用其他 JNIEnv 函数,则会导致程序死掉。
--> android_media_MediaScanner.cpp::MyMediaScanner的scanFile函数

JNIEnv 提供了三个函数帮助在代码中截获和修改这些异常

 
 
public class MediaScanner
{
    //加载对应的jni库,库名为libmedia_jni
    //在实际加载中会扩展成libmedia_jni.so,Windows平台则是media_jni.dll
    static {
        System.loadLibrary("media_jni");
        native_init();
    }

    //声明一个native函数,native为Java关键字,表示将由JNI层完成
    private static native final void Native_init();
}
//这个函数是native_init的JNI层实现
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("android/media/MediaScanner");

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    }
}
  #include <jni.h>   //必须包含这个文件,否则编译不通过  
  #ifndef _Included_android_media_MediaScanner
  #define _Included_android_media_MediaScanner
  #ifdef __cplusplus
  extern "C" {
  #endif

  //native_init对应的JNI函数
  //Java层函数名中如果有一个"_",被转换成JNI之后就变成了"_l"。  
  JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass);
  }
  #endif
  #endif
typedef struct {
    //Java中native函数的名字,不用携带包的路径,例如"native_init"。
    const char* name;
     //Java函数的签名信息,用字符串表示,时参数类型和返回值类型的组合
         const char* signature;
         void*       fnPtr;      //JNI层对应函数的函数指针, void* 类型
} JNINativeMethod

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 566
活跃值: (990)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
样本方便发一个嘛,大佬
2020-5-29 10:34
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
支持一下 总结得不错
2021-2-13 09:44
0
游客
登录 | 注册 方可回帖
返回
//