JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
这里教大家怎么再Android的Service中自定义jni,Service以之前一篇博文为例:
Android P中如何自定义一个系统Service
JustArtService.java中添加native方法,在binder调用方法中调用native方法,目的是其他进程应用也可以访问这个方法。
public class JustArtService extends IJustArt.Stub{
private final static String TAG = "JustArtService";
// 定义jni访问接口
private static native String nativeGetWifiInfo();
public JustArtService(){
}
@Override
public String getAllWifiInfo() throws RemoteException {
Slog.d(TAG,"this is a new service for debug");
//调用jni方法
String str = nativeGetWifiInfo();
Slog.d(TAG,str==null?"null info":str);
return str;
}
}
我在自定义系统service基础上来写这篇博文,所以我将.cpp文件创建在frameworks/base/services/core/jni/com_android_server_justart_JustArtService.cpp
#define LOG_TAG "JustArtService"
#include
#include
//【a】导入jni依赖包(必须)
#include
#include
#include
......
namespace android
{
static char* read_file(const char *path)
{
int fd = -1, size;
static char str[1000];
fd = open(path, O_RDONLY);
if(fd==-1)
{
ALOGE("file not found or no permission");
return NULL;
}
size = read(fd, str, sizeof(str));
close(fd);
//LOGI(buffer);
return str;
}
//【b】对应java端的nativeGetWifiInfo
static jstring android_server_justart_GetAllWifiInfo(JNIEnv *env, jobject clazz)
{
const char *path = "/data/misc/wifi/WifiConfigStore.xml";
char *result = read_file(path);
return env->NewStringUTF(result);
}
//【c】 设置java和jni方法的对应关系
static JNINativeMethod method_table[] = {
{ "nativeGetWifiInfo","()Ljava/lang/String;", (void *)android_server_justart_GetAllWifiInfo},
};
//【d】jni注册java端的service,可以理解为绑定service
int register_android_server_JustArtService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/justart/JustArtService",
method_table, NELEM(method_table));
}
}
导入常用的函数包,比如d中的jniRegisterNativeMethods()方法。
定义jni函数对应Java端方法一般规则如下:
static jstring android_server_justart_GetAllWifiInfo(JNIEnv *env, jobject clazz)
① 返回类型jstring
字符 | Java类型 | JNI类型 | C++类型 | 大小 |
---|---|---|---|---|
V | void | void | void | - |
Z | boolean | jblloean | unsigned char | 无符号8位 |
B | byte | jbyte | char | 有符号8位 |
C | char | jchar | unsigned short | 无符号16位 |
S | short | jshort | short | 有符号16位 |
I | int | jint | int | 有符号32位 |
J | long | jlong | long long | 有符号64位 |
F | float | jfloat | float | 32位 |
D | double | jdouble | double | 64位 |
Java类型 | 原生类型 |
---|---|
java.lang.Class | jclass |
java.lang.Throwable | jthorwable |
java.lang.String | jstring |
Other objects | jobjects |
java.lang.Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbooleanArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
Other arrays | jarray |
② 函数命名 android_server_justart_GetAllWifiInfo
一般用java类中的包名类名加方法名,其实这个不是严格限制的,只需要在method_table[]中设置好对应关系即可。
③ 函数参数
默认添加2个参数:JNIEnv *env, jobject clazz,如果java端方法没有参数则只写默认参数,如果java端有参数,后面按照java参数顺序依照①命名定义其他参数。
static JNINativeMethod method_table[] = {
{ "nativeGetWifiInfo","()Ljava/lang/String;", (void *)android_server_justart_GetAllWifiInfo};
};
method_table[]中的每一个方法一个代码块,中间用“;”隔开,代码块内部第一个参数写Java方法名,第二个参数描述了函数的参数和返回值,第三个参数是函数指针指向jni 函数名,一般都是(void *)形式。
这里比较难懂的就是第二个参数:
“()Ljava/lang/String;” 它是一种对函数返回值和参数的编码。这种编码叫做JNI字段描述符(JavaNative Interface FieldDescriptors)。
数据类型 | 字符 |
---|---|
void | V |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
object | 以"L"开头,以";“结尾,中间是用”/" 隔开的包及类名。比如:String ⇒ Ljava/lang/String; |
格式:(参数描述符)返回类型
JNI方法描述符,主要就是在括号里放置参数,在括号后面放置返回类型,当一个函数不需要返回参数类型时,就使用”V”来表示,具体的定义规则如下:
int register_android_server_JustArtService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/justart/JustArtService",
method_table, NELEM(method_table));
}
定义register方法,这个方法需要在onLoad中注册。这个方法直接返回 jniRegisterNativeMethods函数,jniRegisterNativeMethods函数有4个参数,直接从第二个参数来看,对应java端的方法,包名类名中间用“/”分割,第三个方法C中的函数对应代码块数组。
接下来看注册jni.
找到onload.cpp文件添加如下代码:
备注:onload.cpp一般在jni根目录下,比如系统Service对应的jni就在frameworks/base/services/core/jni/onload.cpp
namespace android {
...
// add by justart for new jni start
int register_android_server_JustArtService(JNIEnv *env);
// add by justart for new jni end
...
}
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
...
// add by justart for new jni start
register_android_server_JustArtService(env);
// add by justart for new jni end
...
}
namespace android 中间注册register_android_server_JustArtService(JNIEnv *env);
JNI_OnLoad函数中间调用register_android_server_JustArtService(env)。
注意:函数名必须和上面2-d中的函数名、参数相同。
最后一步就是我们自定义的jni编译生效,需要在Android.bp中srcs添加我们新建的jni文件。
Android.bp一般和onload.cpp在同一路径下,比如系统Service对应的Android.bp就在frameworks/base/services/core/jni/Android.bp
cc_library_static {
...
srcs: [
...
"com_android_server_justart_JustArtService.cpp",
"onload.cpp"
],
...
}
srcs:数组中添加我们定义的.cpp文件,onload.cpp也在其中。
到此自定义JNI就结束了。可以编译试试效果,最好在编译之前执行下make update-api 。