Android P自定义JNI

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 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章