Android P 自定义 jni
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
1. 定义native方法
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;
}
}
2. 新建jni文件
我在自定义系统service基础上来写这篇博文,所以我将.cpp文件创建在frameworks/base/services/core/jni/com_android_server_justart_JustArtService.cpp
#define LOG_TAG "JustArtService"
#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
//【a】导入jni依赖包(必须)
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include <stdio.h>
......
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));
}
}
a. 导入jni依赖包
导入常用的函数包,比如d中的jniRegisterNativeMethods()方法。
b. 对应java端的native方法
定义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参数顺序依照①命名定义其他参数。
c. 设置java和jni方法的对应关系
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”来表示,具体的定义规则如下:
- "()Ljava/lang/String;"就是表示String func();
- "(ILjava/lang/Class;)J"就是表示long func(int i, Class c);
- "([B)V"就是表示void func(byte[] bytes);
- objects对象以"L"开头,以";“结尾,中间是用”/" 隔开的包及类名。比如:Ljava/lang/String;如果是嵌套类,则用$来表示嵌套。例如:
"(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"就是表示 boolean func(String str,FileUtils.FileStatus status) - 数组类型的简写,则用"[“加上如表A所示的对应类型的简写形式进行表示就可以了。比如:
int[],就需要表示为这样”[I"。
如果多个数组double[][][]就需要表示为这样 “[[[D”。
也就是说每一个方括号开始,就表示一个数组维数。多个方框后面,就是数组的类型。
d. jni注册java端的service
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.
3.onLoad中注册当前定义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中的函数名、参数相同。
4. Android.bp 添加jni
最后一步就是我们自定义的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 。