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 。