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 。

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