JNI定义了Java和C/C++之间的通信规范,当中存在不少机械式定义,像C/C++中原生函数名的一部分是Java中package名称。一旦相互调用的函数多了,依旧用手工去处理,不但浪费时间还容易出错,于是java提供一个叫javah的实用程序帮助开发者把*.java声明的JNI方法转化成C/C++头文件*.h。
javah只是实现了机械式定义中部分提取,一些厂家不满足那点处理,甚至要加上非JNI规范的私有处理。举个例子,让Java代码出现@JNINamespace关键字,表示接下调用的C/C++原生函数是在这个命名空间中实现。@CalledByNative则用于加在函数前,表示这Java函数将被C/C++调用。——为实现这些目标,google开发了一个用python语言写的脚本包,jni_generator.py是这个脚本包中用户要调用的唯一命令。简单来说,jni_generator.py是一个跨平台的、功能增强的javah,它能够识别java文件中被@CalledByNative修饰、以及被native关键字修饰且函数名以native开头的函数,并生成对应的jni方法以及简单的函数声明(具体业务需要自己去实现)。
获取jni_generator.py
这里讲述如何从webrtc源码得到脚本包。jni_generator脚本本身是在webrtc的源码目录里,当拉取到webrtc的源码后,本身这个脚本就可以运行了(但是从谷歌官方上下载下来的源码没有找到这个文件,所以还是建议用前边我贴的超链接)。但webrtc源码及依赖库都很大,要想分离使用这个脚本,最简单的就是只取出这个脚本用到的python依赖,这样就可以使用这个脚本独立运行了。
提取的步骤很简单。这里假定webrtc源码目录为src。
- 提取src/base/android/jni_generator/这个目录里的所有文件,并保留目录层次。这是jni_generator.py所在的目录。
- 提取上面脚本依赖的库,提取src/build/目录里的所有文件及目录,这里全部提取只是为了方便快速,其实很多文件是不必要的。
- 继续提取依赖,提取src/buildtools/目录的所有文件及目录,理由同2.
- 最后要提取的是src/third_party/catapult目录
到此为止,一个可以独立使用的jni_generator.py就完成了。
使用jni_generator.py
以下只列出jni_generator.py常用的四个参数。
- --input_file。指示要解析的单个*.java或*.class,生成的*.h放在“--output_dir”指定的目录。是*.class时,jni_generator.py会先调用javap反編译出*.java。
- --output_dir。指示生成的*.h要存放到的目录。
- --jar_file。此次要解析一个*.jar文件,这时“--input_file”表示的是该jar中某个预編译出的.class。
- --javap(默认值:javap)。javap在硬盘上的完整路径,包括文件名javap.exe。
根据不同类型的输入文件,使用jni_generator.py可分为两类,一是从*.java输入,二是从*.class输入。
jni_generator.py --input_file MainActivity.java --output_dir ./jni
示例输入是*.java。MainActivity.java已是源码文件,jni_generator分析文件中文本就能生成MainActivity_jni.h,然后把它放在“./jni”目录。
jni_generator.py --input_file $mnt/java/lang/Integer.class --output_dir generated_external_classes_jni/jni
jni_generator.py --jar_file rt.jar --input_file java/lang/Integer.class --output_dir generated_external_classes_jni/jni
示例中两条语句都可认为是从*.class输入,并且实现同样功能。Integer.class是jre/jdk自提供的一个class,它和基它类型class一块被放入rt.jar。
第一条语句中,硬盘真实存在着--input_file表示的Integer.class,像把rt.jar解压到一个目录,然后让指向那里的Integer.class。*.class是从*.java編译生成,jni_generator.py需要把它反編译成*.java,再生成*.h。反編译用的是javap,所以必须要让jni_generator能知道javap在哪里。一种方法是把javap所在路径加入PATH环境变量,第二种则是用--javap专门指示javap在哪里。
第二条语句中,Integer.class放在了*.jar。--jar_file指向这个jar,--input_file则是Integer.class在jar中路径。相比第一条语句,这种情况多了第一个步骤,解压rt.jar,一旦jre/jdk安装有异常,极可能导致解压失败,为此要能成功的条件会比直接在--input_file输入.class时更严格。
如何在脚本中批量生成jni方法
以下脚本是我在AS工程中使用的脚本
#!/bin/zsh
#参数1是期望的结尾,参数2是文件夹
function endWith(){
for i in $2;
do
if [[ $i == *$1 ]]
then
return 1
fi
done
return 0
}
function read_dir(){
for file in `ls -a $1`
do
if [ -d $1"/"$file ]
then
if [[ $file != '.' && $file != '..' ]]
then
read_dir $1"/"$file
fi
else
child=$1"/"$file
endWith .java $child
if [[ $? == 1 ]]
then
echo generate $child"\`s" _jni.h
#使用时,需要把../../.././jni_generator.py更换为你自己的路径
python ../../.././jni_generator.py --input_file $child --output_dir ./app/src/main/cpp
fi
fi
done
}
#java文件所在的路径
jniPath="./app/src/main/java/io/agora/jnigeneratortest/"
read_dir $jniPath
下边贴一下成功生成的图片
注意:对于不包含native方法的java文件,jni_generator.py会报错,不过无伤大雅,不会影响其他java文件。
我的AS工程下载地址:https://download.youkuaiyun.com/download/u013908616/13970014
请大佬们多多指教,多谢。